2013-02-13 4 views
11

У меня есть ConcurrentHashMap, где я делаю следующее:Использование ConcurrentHashMap, когда требуется синхронизация?

sequences = new ConcurrentHashMap<Class<?>, AtomicLong>(); 

if(!sequences.containsKey(table)) { 
    synchronized (sequences) { 
     if(!sequences.containsKey(table)) 
      initializeHashMapKeyValue(table); 
    } 
} 

Мой вопрос - это ненужным, чтобы сделать дополнительный

if(!sequences.containsKey(table)) 

Проверьте внутри synschronized блока, так что другие потоки обыкновение инициализировать тот же HashMap стоимость?

Возможно, проверка необходима, и я делаю это неправильно? Мне кажется немного глупо, что я делаю, но я думаю, что это необходимо.

+0

Если вы хотите выполнить ConcurrentHashMap, что является требованием блокировки синхронного объекта на этом экземпляре. – Anil

+0

Да, это необходимо. – assylias

ответ

20

Все операции на ConcurrentHashMap являются потокобезопасными, но поточно-безопасные операции не являются составными. Вы пытаетесь сделать атомарную пару операций: проверяя что-то на карте и, если ее там нет, поместите что-то там (я полагаю). Поэтому ответ на ваши вопросы: да, вам нужно еще раз проверить, и ваш код выглядит нормально.

+1

В одном случае, когда вы можете пропустить дополнительную проверку, вы не возражаете, что 'initializeHashMapKeyValue (table)' будет сделано более одного раза за одно и то же значение 'table'. Это может быть связано с тем, что 'initializeHashMapKeyValue' не имеет побочных эффектов и довольно дешев. –

3

Вы не можете получить эксклюзивный замок с ConcurrentHashMap. В таком случае вам лучше использовать Synchronized HashMap.

Существует уже атомальный метод для помещения внутри ConcurrentHashMap, если объект еще не существует; putIfAbsent

+1

Тот факт, что вы не можете получить блокировку на карте, не мешает вам синхронизировать доступ к карте. – assylias

+0

согласен ... но добавляет ли он какую-либо ценность. На самом деле у вас будет неправильное впечатление. –

+1

ConcurrentMap достигнет гораздо лучшей параллелизма - так что если 'sequence.containsKey (table)' в общем случае истинно, использование параллельной карты все же имеет смысл, если производительность является проблемой. – assylias

1

Я вижу, что вы там делали ;-) Вопрос: вы сами это видите?

Прежде всего, вы использовали что-то, называемое «Double checked lock pattern». Если у вас есть быстрый путь (первый содержит), который не нуждается в синхронизации, если он выполняется, и медленный путь, который необходимо синхронизировать, потому что вы выполняете сложную операцию. Ваша операция состоит в проверке, находится ли что-то внутри карты, а затем помещено там что-то/инициализируется. Поэтому не имеет значения, что ConcurrentHashMap является потокобезопасным для одиночной операции, потому что вы выполняете две простые операции, которые должны обрабатываться как единое целое, поэтому да, этот синхронизированный блок правильный, и на самом деле его можно синхронизировать с помощью чего-либо еще, например, this.

16

Вы должны использовать методы putIfAbsentConcurrentMap.

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-можно избежать ненужного создания AtomicLong:

public long addTo8(String key, long value) { 
    return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value); 
} 
+1

Может иметь смысл использовать шаблон OP, если 'initializeHashMapKeyValue' занимает нетривиальное количество времени или имеет побочные эффекты. – assylias

+0

@assylias - Если побочные эффекты неизбежны, то я думаю, что соглашусь, но есть много способов избежать побочных эффектов/побочных эффектов с использованием заводских методов et al. Я использовал бы этот образец, где это возможно, потому что он гарантированно свободен от условий гонки, тогда как шаблон с двойной проверкой блокировки славится очень тонкими проблемами. – OldCurmudgeon

+1

У блокировки с двойной проверкой могут быть проблемы, но не в этом конкретном примере. Обратите внимание, что я не говорю, что ваш подход не подходит - я просто говорю, что подход OP может быть лучше при некоторых обстоятельствах. – assylias

1

В Java 8 вы должны иметь возможность заменить блокировку с двойным замком на .computeIfAbsent:

sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k)); 
0

Создать файл с именем dictionary.TXT со следующим содержанием:

a 
as 
an 
b 
bat 
ball 

Здесь мы имеем: Количество слов, начиная с "а": 3

Количество слов, начиная с "Ъ": 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(); 
    } 
} 

}

Вы бы видели отсчеты изменяются, как показано ниже:

{а = 2, Ь = 3}

Общее количество слов: 6

Завершенный в 77 миллисекунд

{a = 3, b = 3}

Всего слов: 6

Теперь прокомментируйте ConcurrentHashMap в строке 13, раскомментируйте строку над ней и запустите программу еще раз.

Вы увидите детерминированные подсчеты.

Смежные вопросы