Java ConcurrentHashMap动作原子性

ins*_*n-e 12 java concurrency multithreading concurrenthashmap concurrent-programming

这可能是一个重复的问题,但我在一本关于并发的书中找到了这部分代码.这据说是线程安全的:

ConcurrentHashMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    while (true) {
        Integer currentCount = counts.get(thing);
        if (currentCount == null) {
            if (counts.putIfAbsent(thing, 1) == null)
                break;
        } else if (counts.replace(thing, currentCount, currentCount + 1)) {
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从我的(并发初学者)的角度来看,线程t1和线程t2都可以读取currentCount = 1.然后两个线程都可以将地图的值更改为2.有人可以解释我代码是否正常?

ysh*_*vit 11

诀窍在于replace(K key, V oldValue, V newValue)为您提供原子性.从文档(强调我的):

当前映射到给定值时才替换键的条目.......动作以原子方式执行.

关键词是"原子地".在内部replace,"检查旧值是否符合我们的预期,并且只有它是替换它"发生在一个单一的工作块中,没有其他线程可以与它交错.实现取决于它需要做什么同步以确保它提供这种原子性.

因此,两个线程都不currentAction == 1能从replace函数中看到.其中一个将其视为1,因此其调用replace将返回true.另一个将它看作2(因为第一次调用),因此返回false - 并循环返回再次尝试,这次使用新的值currentAction == 2.

当然,可能是第三个线程同时将currentAction更新为3,在这种情况下,第二个线程将继续尝试,直到它足够幸运,没有任何人跳过它.


Pet*_*rey 6

有人可以解释我代码是否合适?

除了yshavit的答案,你可以避免使用computeJava 8中添加的循环来编写自己的循环.

ConcurrentMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    counts.compute(thing, (k, prev) -> prev == null ? 1 : 1 + prev);
}
Run Code Online (Sandbox Code Playgroud)