Синхронизированные и локальные копии переменных

Я смотрю на какой-то унаследованный код, который имеет следующую идиому:

Map<String, Boolean> myMap = someGlobalInstance.getMap();
synchronized (myMap) {
    item = myMap.get(myKey);
}

Предупреждение, которое я получаю от проверки кода Intelli-J:

Synchronization on local variable 'myMap'

Является ли это подходящей синхронизацией и почему?

Map<String, Boolean> myMap = someGlobalInstance.getMap();
synchronized (someGlobalInstance.getMap()) {
    item = myMap.get(myKey);
}

Ответы

Ответ 1

Причина, по которой это помечена как проблема, заключается в том, что синхронизация по локальным переменным обычно является плохой идеей.

Если объект, возвращаемый someGlobalInstance.getMap(), всегда один и тот же, тогда синхронизированный блок фактически использует этот монитор с квази-глобальными объектами, а код создает ожидаемый результат.

Я также согласен с предложением использовать синхронизированную оболочку, если вам нужно только синхронизировать вызовы get()/put() и не иметь более крупных синхронизированных блоков. Но убедитесь, что карта доступна только через обертку или у вас будет еще один шанс на ошибки.

Также обратите внимание, что если someGlobalInstance.getMap() не возвращает один и тот же объект все время, то даже ваш второй пример кода будет работать некорректно, это может быть даже хуже, чем исходный код, поскольку вы можете синхронизировать другой объект, чем вы вызываете get() on.

Ответ 2

Я думаю, что код может быть звуковым, в зависимости от того, что делает метод getMap(). Если он содержит ссылку на экземпляр, который должен быть разделен между потоками, это имеет смысл. Предупреждение не имеет значения, поскольку локальная переменная не инициализируется локально.

Ответ 4

Алекс прав в том, что добавление синхронизированной обертки при вызове Collections.synchronizedMap(Map) является типичным подходом здесь. Однако, если вы примете такой подход, могут возникнуть ситуации, когда вам нужно синхронизировать блокировку Map; например при итерации по карте.

Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<String, String>());

// Synchronized on map to prevent ConcurrentModificationException whilst iterating.
synchronized (syncMap) {
  for (Map.Entry<String, String> entry : syncMap.entrySet()) {
    // Do work
  }
}

В вашем примере предупреждение от IDEA можно игнорировать, так как очевидно, что ваша локальная переменная: Map извлекается из другого места (someGlobalInstance), а не создается в этом методе и поэтому может быть доступна для доступа из других потоков.