Как да попълня concurrenthashmap от множество нишки?

Имам ConcurrentHashMap, който попълвам от множество нишки.

private static Map<DataCode, Long> errorMap = new ConcurrentHashMap<DataCode, Long>();

public static void addError(DataCode error) {
    if (errorMap.keySet().contains(error)) {
        errorMap.put(error, errorMap.get(error) + 1);
    } else {
        errorMap.put(error, 1L);
    }
}

Моят метод по-горе addError се извиква от множество нишки, които попълват errorMap. Не съм сигурен дали това е безопасно за нишка? Има ли нещо лошо, което правя тук?

Всяко обяснение защо може да пропуска актуализации ще ми помогне да разбера по-добре.


person john    schedule 19.06.2015    source източник


Отговори (1)


Дали това е безопасно зависи от това какво имате предвид. Няма да хвърля изключения или да повреди картата, но може да пропусне актуализации. Обмисли:

  1. Thread1: errorMap.get(error) връща 1
  2. Thread2: errorMap.get(error) връща 1
  3. Thread1: errorMap.put(грешка, 1+1);
  4. Thread2: errorMap.put(грешка, 1+1);

Подобна надпревара съществува около операцията keySet().contains(error). За да коригирате това, ще трябва да използвате атомарни операции за актуализиране на картата.

На Java 8 това е лесно:

errorMap.compute(error, oldValue -> oldValue == null ? 1L : oldValue + 1L);

При по-стари версии на Java трябва да използвате цикъл за сравняване и актуализиране:

Long prevValue;
boolean done;
do {
  prevValue = errorMap.get(error);
  if (prevValue == null) {
    done = errorMap.putIfAbsent(error, 1L);
  } else {
    done = errorMap.replace(error, prevValue, newValue);
  }
} while (!done);

С този код, ако две нишки се състезават, едната може в крайна сметка да опита повторно своята актуализация, но накрая ще получат правилната стойност.

Като алтернатива можете също да използвате AtomicLongMap, който прави цялата магия за безопасност на нишките вместо вас и получава по-висока производителност (чрез избягване на всички тези операции с бокс, наред с други неща):

errorAtomicLongMap.incrementAndGet(error);
person bdonlan    schedule 19.06.2015
comment
Благодаря за помощта. Сега има смисъл. Ако е възможно, можете ли да предоставите пример как мога да използвам Guava AtomicLongMap за това? - person john; 19.06.2015
comment
@david, добавен пример. Просто, нали? :) - person bdonlan; 19.06.2015
comment
Какво? много е просто.. Страхотно. Освен това сега как ще изглежда errorAtomicLongMap декларацията? Смених картата с AtomicLongMap, получих грешка. - person john; 19.06.2015
comment
Не е напълно спад в замяната. Ще трябва да го декларирате просто като AtomicLongMap<DataCode> и когато четете от него, използвайте или get(error), или метода .asMap() - person bdonlan; 19.06.2015
comment
Виждам, че когато опитах по този начин, все още получавам грешка: The constructor AtomicLongMap<DataCode>() is undefined. Това е, което опитвам: private static AtomicLongMap<DataCode> errorHolder = new AtomicLongMap<DataCode>(); Никога не съм използвал това преди, така че имам известни съмнения. - person john; 19.06.2015
comment
Използвайте функцията за създаване: docs.guava-libraries.googlecode.com/git/javadoc/com/google/ - person bdonlan; 19.06.2015