Атомное обновление без блокировки для неизменной карты

Учитывая Javaslang/Vavr неизменяемую карту и функция, которая обновляет эту карту:

private Map<Foo, Bar> myMap = HashMap.empty();

public void setBar(Foo foo, Bar bar) {
  myMap = myMap.put(foo, bar);
}

Как я могу гарантировать, что для двух одновременных вызовов setBar() для разных ключей Foo будут записаны их обновления?

// thread A
setBar(fooA, barA)

// thread B
setBar(fooB, barB)

Похоже, что существует риск того, что вызовы будут чередоваться так, что:

  1. поток A получает {}
  2. поток B получает {}
  3. поток B вычисляет {} + fooB -> barB = {(fooB -> barB)}
  4. поток B устанавливает myMap в {(fooB -> barB)}
  5. поток A вычисляет {} + fooA -> barA = {(fooA -> barA)}
  6. поток A устанавливает myMap в {(fooA -> barA)}
  7. обновление потока B потеряно

Используя AtomicReference, я пришел к следующему, более или менее основанному на методах ConcurrentStack в разделе «Неблокирующие алгоритмы» Java Concurrency in Практика.

private AtomicReference<Map<Foo, Bar>> myMap = 
  new AtomicReference<>(HashMap.empty());

public void setBar(Foo foo, Bar bar) {
  Map<Foo, Bar> myMap0;
  Map<Foo, Bar> myMap1;
  do {
    myMap0 = myMap.get();
    myMap1 = myMap0.put(foo, bar);
  } while (!myMap.compareAndSet(myMap0, myMap1));
}

Это правильно? И если да, то является ли это такой же хорошей реализацией, как я, вероятно, получу, или есть что-то более простое (например, какой-то Java 8 AtomicReference API, который мне не хватает, который реализует этот шаблон)?


person David Moles    schedule 24.05.2017    source источник


Ответы (1)


В этом случае хорошо использовать AtomicReference. Вы можете использовать метод быстрого доступа

public void setBar(Foo foo, Bar bar) {
    myMap.updateAndGet(map -> map.put(foo, bar)));
}

вместо. См. javadoc для AtomicReference.updateAndGet. Реализация Java по умолчанию точно такая же, как у вас в Java 8.

person Nándor Előd Fekete    schedule 24.05.2017