#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();
}