Додавання до AtomicInteger в рамках ConcurrentHashMap - java, багатопоточність, паралельність, java.util.concurrent, concurrenthashmap

Я визначив таке

private ConcurrentMap<Integer, AtomicInteger>  = new ConcurrentHashMap<Integer, AtomicInteger>();

private void add() {
staffValues.replace(100, staffValues.get(100), new AtomicInteger(staffValues.get(100).addAndGet(200)));
}

Після тестування значення, які я отримую, не єОчікується, і я думаю, що тут існує стан перегонів. Хто-небудь знає, чи це буде розглянуто як "thread-safe", обернувши виклик у функції заміни?

Відповіді:

2 для відповіді № 1

Існує кілька проблем з кодом. Найбільшим є те, що ви ігноруєте рентабельність ConcurrentHashMap.replace: якщо заміна не відбувається (через те, що інший потік зробив заміну паралельно), ви просто продовжите неначе це сталося. Це головна причина того, що ви отримуєте неправильні результати.

Я також думаю, що це дизайн помилка мутувати AtomicInteger а потім негайно замінити його іншим AtomicInteger; навіть якщо ви можете отримати цю роботу, для цього просто немає підстав.

Нарешті, я не думаю, що ви повинні зателефонувати staffValues.get(100) двічі Я не думаю, що це викликає помилку в поточному коді - ваша коректність залежить тільки від другого дзвінка, що повертає "новий" результат, ніж перший, який я думаю є фактично гарантується ConcurrentHashMap - але це тендітне і тонке і заплутане. Загалом, коли ви телефонуєте ConcurrentHashMap.replace, третій аргумент - це те, що ви обчислили за допомогою другого.

Загалом, ви можете спростити свій код або не використовуючи AtomicInteger:

private ConcurrentMap<Integer, Integer> staffValues = new ConcurrentHashMap<>();

private void add() {
final Integer prevValue = staffValues.get(100);
staffValues.replace(100, prevValue, prevValue + 200);
}

або не використовуючи replace (та можливо навіть не ConcurrentMap, залежно від того, як ще ви торкаєтеся цієї карти):

private Map<Integer, AtomicInteger> staffValues = new HashMap<>();

private void add() {
staffValues.get(100).addAndGet(200);
}

1 для відповіді № 2

Хороший спосіб для вирішення таких ситуацій - це використання computeIfAbsent метод (не compute метод, який @ the8472 рекомендує)

The computeIfAbsent приймає 2 аргументи, ключ і a Function<K, V> що буде викликатись лише тоді, коли відсутнє існуюче значення. Оскільки в AtomicInteger є безпека потоку для збільшення з декількох потоків, ви можете легко використовувати його таким чином:

staffValues.computeIfAbsent(100, k -> new AtomicInteger(0)).addAndGet(200);

0 для відповіді № 3

Вам не потрібно користуватися replace(). AtomicInteger - це змінне значення, яке не потрібно замінювати, коли ви хочете збільшити його. Насправді addAndGet вже збільшує його на місці.

Замість цього користуйтеся compute щоб поставити за замовчуванням значення (мабуть 0) на карту, коли ніхто не присутній, інакше отримати попереднє значення і приріст цього.

Якщо, з іншого боку, ви хочете застосувати незмінні значення Integer випадків замість AtomicInteger на карту та оновлювати їх за допомогою операцій atomic compute / replace / merge.


Схожі запитання
Найбільш популярний