Использование ConcurrentHashMap, когда требуется синхронизация?
У меня есть ConcurrentHashMap, где я делаю следующее:
sequences = new ConcurrentHashMap<Class<?>, AtomicLong>();
if(!sequences.containsKey(table)) {
synchronized (sequences) {
if(!sequences.containsKey(table))
initializeHashMapKeyValue(table);
}
}
Мой вопрос - нет необходимости делать дополнительные
if(!sequences.containsKey(table))
Проверьте внутри синхронизированного блока, чтобы другие потоки не инициализировали одно и то же значение hashmap?
Может быть, проверка нужна, и я делаю это неправильно? Мне кажется немного глупо, что я делаю, но я думаю, что это необходимо.
Ответы
Ответ 1
Операции Все на ConcurrentHashMap являются потокобезопасными, но поточно-безопасные операции не являются составными. Вы пытаетесь сделать атомарную пару операций: проверяя что-то на карте и, если она там не там, поместите что-то там (я полагаю). Поэтому ответ на ваши вопросы: да, вам нужно снова проверить, и ваш код выглядит нормально.
Ответ 2
Вы должны использовать putIfAbsent методы ConcurrentMap
.
ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong> ();
public long addTo(String key, long value) {
// The final value it became.
long result = value;
// Make a new one to put in the map.
AtomicLong newValue = new AtomicLong(value);
// Insert my new one or get me the old one.
AtomicLong oldValue = map.putIfAbsent(key, newValue);
// Was it already there? Note the deliberate use of '!='.
if ( oldValue != newValue ) {
// Update it.
result = oldValue.addAndGet(value);
}
return result;
}
Для функциональных пуристов среди нас вышеуказанное может быть упрощено (или, возможно, комплексифицировано) до:
public long addTo(String key, long value) {
return map.putIfAbsent(key, new AtomicLong()).addAndGet(value);
}
И в Java 8 мы можем избежать ненужного создания AtomicLong
:
public long addTo8(String key, long value) {
return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value);
}
Ответ 3
Вы не можете получить эксклюзивный замок с ConcurrentHashMap. В таком случае вам лучше использовать Synchronized HashMap.
Внутри ConcurrentHashMap уже существует атомный метод, если объект еще не существует; putIfAbsent
Ответ 4
Я вижу, что вы там делали;-) Вопрос: вы сами видите?
Прежде всего, вы использовали что-то под названием "Double checked lock pattern". Если у вас есть быстрый путь (первый содержит), который не нуждается в синхронизации, если он выполняется, и медленный путь, который необходимо синхронизировать, потому что вы выполняете сложную операцию. Ваша операция состоит в проверке, находится ли что-то внутри карты, а затем помещено там что-то/инициализируется. Поэтому не имеет значения, что ConcurrentHashMap является потокобезопасным для одиночной операции, потому что вы выполняете две простые операции, которые нужно рассматривать как единицу, поэтому да, этот синхронизированный блок правильный, и на самом деле он может быть синхронизирован любым другим, например, this
.
Ответ 5
В Java 8 вы можете заменить блокировку с двойным флажком на .computeIfAbsent
:
sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k));
Ответ 6
Создайте файл с именем dictionary.txt со следующим содержимым:
a
as
an
b
bat
ball
Здесь мы имеем:
Количество слов, начинающихся с "a": 3
Количество слов, начинающихся с "b": 3
Общее количество слов: 6
Теперь выполните следующую программу как: java WordCount test_dictionary.txt 10
public class WordCount {
String fileName;
public WordCount(String fileName) {
this.fileName = fileName;
}
public void process() throws Exception {
long start = Instant.now().toEpochMilli();
LongAdder totalWords = new LongAdder();
//Map<Character, LongAdder> wordCounts = Collections.synchronizedMap(new HashMap<Character, LongAdder>());
ConcurrentHashMap<Character, LongAdder> wordCounts = new ConcurrentHashMap<Character, LongAdder>();
Files.readAllLines(Paths.get(fileName))
.parallelStream()
.map(line -> line.split("\\s+"))
.flatMap(Arrays::stream)
.parallel()
.map(String::toLowerCase)
.forEach(word -> {
totalWords.increment();
char c = word.charAt(0);
if (!wordCounts.containsKey(c)) {
wordCounts.put(c, new LongAdder());
}
wordCounts.get(c).increment();
});
System.out.println(wordCounts);
System.out.println("Total word count: " + totalWords);
long end = Instant.now().toEpochMilli();
System.out.println(String.format("Completed in %d milliseconds", (end - start)));
}
public static void main(String[] args) throws Exception {
for (int r = 0; r < Integer.parseInt(args[1]); r++) {
new WordCount(args[0]).process();
}
}
}
Вы увидите, что подсчеты меняются, как показано ниже:
{a = 2, b = 3}
Общее количество слов: 6
Завершено за 77 миллисекунд
{a = 3, b = 3}
Общее количество слов: 6
Теперь закомментируйте ConcurrentHashMap в строке 13, раскомментируйте строку над ней и запустите программу еще раз.
Вы увидите детерминированные подсчеты.