Как безопасно изменять значения в Java HashMaps одновременно?

#java #concurrency #hashmap

#java #параллелизм #hashmap

Вопрос:

У меня есть блок Java-кода, который выглядит примерно так, который я пытаюсь распараллелить:

 value = map.get(key);
if (value == null) {
    value = new Value();
    map.put(key,value);
}
value.update();
  

Я хочу заблокировать любому другому потоку доступ к карте с этим конкретным ключом до тех пор, пока не будет value.update() вызван , даже если key отсутствует в наборе ключей. Должен быть разрешен доступ с помощью других ключей. Как я мог бы этого добиться?

Комментарии:

1. Доступ с помощью других ключей также не так безопасен. Добавление ключа / значения (даже другого) включает в себя изменение внутренних данных в коллекции, и в крайних случаях может привести к изменению размера всей карты, пока кто-то пытается извлечь из нее данные. Это может вызвать проблемы. например: lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html

Ответ №1:

Короткий ответ: нет безопасного способа сделать это без синхронизации всего блока. Вы могли бы использовать java.util.concurrent.ConcurrentHashMap, однако, смотрите эту статью для получения более подробной информации. Основная идея заключается в использовании ConcurrentHashMap.putIfAbsent вместо обычного put .

Комментарии:

1. Блокировка с двойной проверкой небезопасна; ее можно настроить для корректной работы начиная с Java 5. Однако вопрос о том, стоит ли это делать, является дискуссионным, и неясно, как это будет применяться к этой проблеме.

2. Ссылка на статью не работает.

Ответ №2:

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

Используйте другую коллекцию, например java.util.concurrent.ConcurrentHashMap, которая представляет собой «хэш-таблицу, поддерживающую полный параллелизм извлечений и настраиваемый ожидаемый параллелизм для обновлений». согласно javadoc.

Ответ №3:

Я бы не стал использовать HashMap, если вам нужно беспокоиться о проблемах с потоками. Воспользуйтесь параллельным пакетом Java 5 и загляните в ConcurrentHashMap.

Ответ №4:

Вы только что описали вариант использования вычислительной карты Guava. Вы создаете его с помощью:

 Map<Key, Value> map = new MapMaker().makeComputingMap(new Function<Key, Value>() {
  public Value apply(Key key) {
    return new Value().update();
  }
));
  

и используйте это:

 Value v = map.get(key);
  

Это гарантирует, что вызовет только один поток update() , а другие потоки заблокируются и будут ждать завершения метода.

Вероятно, на самом деле вы не хотите, чтобы ваше значение имело изменяемый метод обновления для него, но это другое обсуждение.

Ответ №5:

 private void synchronized functionname() {
    value = map.get(key);
    if (value == null) {
        value = new Value();
        map.put(key,value);
    }
    value.update();
}
  

Вы можете узнать больше о синхронизированных методах здесь: Синхронизированные методы

Возможно, вы также захотите изучить ConcurrentHashMap класс, который может подойти для ваших целей. Вы можете увидеть это в JavaDoc.

Комментарии:

1. Это не работает, если существует несколько функций. Синхронизация, зависящая от конкретной переменной, здесь лучше.

Ответ №6:

Посмотрите на параллельную хэш-карту. Он обладает превосходной производительностью даже для однопоточных приложений. Это позволяет одновременно изменять Map из разных потоков без какой-либо необходимости их блокировки.

Ответ №7:

Одной из возможностей является управление несколькими блокировками. Таким образом, вы можете сохранить массив блокировок, который извлекается на основе хэш-кода ключа. Это должно дать вам лучшую сквозную обработку, чем синхронизация всего метода. Вы можете изменять размер массива в зависимости от количества потоков, которые, по вашему мнению, будут обращаться к коду.

 private static final int NUM_LOCKS = 16;
Object [] lockArray = new Object[NUM_LOCKS];
...
// Load array with Objects or Reentrant Locks

...

Object keyLock = lockArray[key.hashcode % NUM_LOCKS];
synchronize(keyLock){
  value = map.get(key);
  if (value == null) {
    value = new Value();
    map.put(key,value);
  }
  value.update();
}