Синхронизированный блок Java против Collections.synchronizedMap

Установлен ли следующий код для правильной синхронизации вызовов на synchronizedMap ?

 public class MyClass { private static Map<String, List> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List>()); public void doWork(String key) { List 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 valuesList = new ArrayList(); valuesList.add(value); synchronizedMap.put(key, valuesList); } } } } 

По моему addToMap() , мне нужен синхронизированный блок в addToMap() чтобы другой stream не вызывал remove() или containsKey() прежде чем я получу вызов put() но мне не нужен синхронизированный блок в doWork() потому что другой нить не может войти в синхронизированный блок в addToMap() прежде чем remove() вернется, потому что я создал карту изначально с Collections.synchronizedMap() . Это верно? Есть лучший способ сделать это?

7 Solutions collect form web for “Синхронизированный блок Java против Collections.synchronizedMap”

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

Однако выполнение двух (или более) операций на карте должно быть синхронизировано в блоке. Так что да – вы правильно синхронизируете.

Если вы используете JDK 6, вы можете проверить ConcurrentHashMap

Обратите внимание на метод putIfAbsent в этом classе.

Существует потенциал для тонкой ошибки в вашем коде.

[ UPDATE: Поскольку он использует map.remove (), это описание не является полным. Я пропустил этот факт в первый раз. 🙁 Спасибо автору вопроса за указание на это. Я оставляю остальное как есть, но изменил инструкцию, чтобы сказать, что есть потенциальная ошибка.]

В doWork () вы получаете значение List из карты поточно-безопасным способом. Впоследствии, однако, вы получаете доступ к этому списку в небезопасном вопросе. Например, один stream может использовать список в doWork (), а другой stream вызывает synchronizedMap.get (key) .add (значение) в addToMap () . Эти два доступа не синхронизированы. Эмпирическое правило состоит в том, что поточные гарантии коллекции не распространяются на ключи или значения, которые они хранят.

Вы можете исправить это, вставив в карту синхронизированный список, например

 List valuesList = new ArrayList(); valuesList.add(value); synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list 

Кроме того, вы можете синхронизироваться на карте, когда вы получаете доступ к списку в doWork () :

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

Последняя опция немного ограничит параллелизм, но несколько более ясная IMO.

Также обратите внимание на ConcurrentHashMap. Это действительно полезный class, но не всегда является подходящей заменой синхронизированным HashMaps. Цитируя из своих Javadocs,

Этот class полностью совместим с Hashtable в программах, которые полагаются на безопасность streamа, но не на детали синхронизации .

Другими словами, putIfAbsent () отлично подходит для атомных вставок, но не гарантирует, что другие части карты не будут меняться во время этого вызова; он гарантирует только атомарность. В вашей примерной программе вы полагаетесь на детали синхронизации (синхронизированной) HashMap для других вещей, кроме put ().

Последнее. 🙂 Эта отличная цитата из Java Concurrency in Practice всегда помогает мне в разработке отладочных многопоточных программ.

Для каждой изменяемой переменной состояния, к которой может обращаться более одного streamа, все обращения к этой переменной должны выполняться с той же блокировкой.

Да, вы правильно синхронизируете. Я объясню это более подробно. Вы должны синхронизировать два или более вызова метода на объекте synchronizedMap только в том случае, когда вы должны полагаться на результаты предыдущих вызовов (-ов) метода в последующем вызове метода в последовательности вызовов методов объекта synchronizedMap. Давайте посмотрим на этот код:

 synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List valuesList = new ArrayList(); valuesList.add(value); synchronizedMap.put(key, valuesList); } } 

В этом коде

 synchronizedMap.get(key).add(value); 

а также

 synchronizedMap.put(key, valuesList); 

вызовы метода полагаются на результат предыдущего

 synchronizedMap.containsKey(key) 

вызов метода.

Если последовательность вызовов метода не была синхронизирована, результат может быть неправильным. Например, thread 1 выполняет метод addToMap() а thread 2 выполняет метод doWork() . Последовательность вызовов метода для объекта synchronizedMap может быть следующей: Thread 1 выполнил метод

 synchronizedMap.containsKey(key) 

и результат « true ». После этого операционная система переключила управление выполнением в thread 2 и выполнила

 synchronizedMap.remove(key) 

После этого управление выполнением было переключено обратно в thread 1 и оно выполнено, например

 synchronizedMap.get(key).add(value); 

полагая, что объект synchronizedMap содержит key и будет NullPointerException , потому что synchronizedMap.get(key) вернет значение null . Если последовательность вызовов метода на объекте synchronizedMap не зависит от результатов друг друга, вам не нужно синхронизировать последовательность. Например, вам не нужно синхронизировать эту последовательность:

 synchronizedMap.put(key1, valuesList1); synchronizedMap.put(key2, valuesList2); 

Вот

 synchronizedMap.put(key2, valuesList2); 

вызов метода не зависит от результатов предыдущего

 synchronizedMap.put(key1, valuesList1); 

вызов метода (не имеет значения, вмешался ли какой-то stream между двумя вызовами метода и, например, удалил key1 ).

Это выглядит правильно для меня. Если бы я что-то изменил, я бы прекратил использовать Collections.synchronizedMap () и синхронизировал все одинаково, просто чтобы сделать его более понятным.

Кроме того, я бы заменил

  if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List valuesList = new ArrayList(); valuesList.add(value); synchronizedMap.put(key, valuesList); } 

с

 List valuesList = synchronziedMap.get(key); if (valuesList == null) { valuesList = new ArrayList(); synchronziedMap.put(key, valuesList); } valuesList.add(value); 

Ознакомьтесь с Multimap Google Collections , например, на странице 28 этой презентации .

Если вы не можете использовать эту библиотеку по какой-либо причине, рассмотрите возможность использования ConcurrentHashMap вместо SynchronizedHashMap ; он имеет отличный putIfAbsent(K,V) с помощью которого вы можете атомизировать список элементов, если он еще не существует. Кроме того, рассмотрите использование CopyOnWriteArrayList для значений карты, если ваши шаблоны использования гарантируют это.

Правильно, как вы синхронизировали. Но есть уловка

  1. Синхронизированная shell, предоставляемая Framework Collection, гарантирует, что метод вызывает Ie add / get / contains, будет работать взаимоисключающим.

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

  1. Вы могли бы использовать одновременную реализацию Карты, доступной в рамках Collection. Преимущества ConcurrentHashMap

а. Он имеет API ‘putIfAbsent’, который будет делать то же самое, но более эффективно.

б. Эффективность: dCocurrentMap просто блокирует ключи, поэтому он не блокирует мир всей карты. Где вы заблокировали ключи, а также значения.

с. Вы могли бы передать ссылку своего объекта карты где-нибудь еще в своей кодовой базе, где вы / другой разработчик в своем тиане могут в конечном итоге использовать его неправильно. Т.е. он может просто добавить () или get () без блокировки на объекте карты. Следовательно, его вызов не будет выполняться взаимно исключающим образом для вашего блока синхронизации. Но использование параллельной реализации дает вам покой, что он никогда не может быть использован / реализован неправильно.

  • Как защитить ресурсы, которые могут использоваться в многопоточной или асинхронной среде?
  • C ++ 0x не имеет семафоров? Как синхронизировать streamи?
  • Как @synchronized блокировка / разблокировка в Objective-C?
  • Как синхронизировать базу данных SQLite на телефоне Android с базой данных MySQL на сервере?
  • В чем разница между атомными / летучими / синхронизированными?
  • Как использовать свойство CancellationToken?
  • Правильный способ синхронизации ArrayList в java
  • Чтение и запись C ++ int Atomic?
  • Подождите, пока флаг = true
  • Как определить, заблокирован ли объект (синхронизирован), чтобы он не блокировался на Java?
  • Статический и нестатический объект блокировки в синхронизированном блоке
  • Давайте будем гением компьютера.