Подтвердить что ты не робот

Зачем синхронизироваться на SynchronizedMap или SynchronizedCollections?

Я имею в виду вопрос, заданный здесь и используя пример кода авторов, теперь мой вопрос

  • Почему автор использует synchronized(synchronizedMap), действительно ли это необходимо, потому что synchronizedMap всегда будет следить за тем, чтобы не было двух потоков, пытающихся выполнить операцию read/put на Map, так зачем нам нужно synchronize на этой карте сам?

Поистине понравилось бы объяснение.


  public class MyClass {
  private static Map<String, List<String>> synchronizedMap =
      Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}
4b9b3361

Ответ 1

зачем нам нужно синхронизировать себя с этим synchronizemap?

Вам может потребоваться синхронизация в уже синхронизированной коллекции, потому что вы выполняете две операции над коллекцией - в вашем примере - containsKey(), а затем put(). Вы пытаетесь защитить от условий гонки в коде, вызывающем коллекцию. Кроме того, в этом случае блок synchronized также защищает значения ArrayList, чтобы несколько потоков могли добавлять свои значения в эти несинхронизированные коллекции.

Если вы посмотрите на код, с которым вы связались, сначала проверьте наличие ключа, а затем поместите значение в карту, если ключ не существует. Вам нужно защитить от 2-х потоков, проверяющих наличие ключа, а затем оба они помещают в карту. Гонка - это первая, которая будет перезаписана предыдущим.

Синхронизированная коллекция защищает себя от множества потоков, искажающих саму карту. Он не защищает от условий логической гонки вокруг нескольких вызовов на карту.

synchronized (synchronizedMap) {
    // test for a key in the map
    if (synchronizedMap.containsKey(key)) {
      synchronizedMap.get(key).add(value);
    } else {
      List<String> valuesList = new ArrayList<String>();
      valuesList.add(value);
      // store a value into the map
      synchronizedMap.put(key, valuesList);
   }
}

Это одна из причин, по которой интерфейс ConcurrentMap имеет putIfAbsent(K key, V value);. Это не требует двух операций, поэтому вам может не потребоваться синхронизация вокруг него.

Btw, я бы переписал вышеприведенный код:

synchronized (synchronizedMap) {
    // test for a key in the map
    List<String> valuesList = synchronizedMap.get(key);
    if (valueList == null) {
      valuesList = new ArrayList<String>();
      // store a value into the map
      synchronizedMap.put(key, valuesList);
    }
    valuesList.add(value);
}

Наконец, если в любом случае большая часть операций на карте должна быть в блоке synchronized, вы также можете не платить за synchronizedMap и просто использовать HashMap всегда внутри блоков synchronized.

Ответ 2

Речь идет не только об обновлении значений synchronizedMap, а о последовательности операций, выполняющих карту. На этом же методе происходят две операции на карте.

Если вы не синхронизируете блок/метод, предположите, что может быть такой случай, как Thread1, выполняющий первую часть, а thread2 выполняет вторую часть, ваша бизнес-операция может привести к странным результатам (даже если обновления для карты синхронизированы)