不可变映射的无锁原子更新

Lock-free atomic update to immutable Map

给定一个 Java俚语/Vavr immutable map,以及一个更新该映射的函数:

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

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

我如何确保针对不同的 Foo 键对 setBar() 的两次并发调用都会记录它们的更新?

// 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,我或多或少地基于 Java Concurrency in Practice 的“非阻塞算法”部分中的 ConcurrentStack 方法提出了以下方法。

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 我错过了实现这个模式)?

在这种情况下使用 AtomicReference 比较好。可以使用快捷方式

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

相反。参见javadoc for AtomicReference.updateAndGet。默认 java 实现与 Java 8.

中的完全相同