并发哈希图中 cmputeIfAbsent 的原子性意味着什么?原子性与同步

Sha*_*awn 5 java multithreading

我知道周围有很多问题computeIfAbsent

具体来说,我正在寻找的是理解并发哈希映射的原子性声明。

来自JavaDoc

整个方法调用都是原子执行的,因此每个键最多应用该函数一次。

如果两个线程尝试computeIfAbsent使用不同的键执行,并发现在这两种情况下映射都不包含它们,如果缺少函数,计算结果的执行是否可能是并发的?我知道如果两个线程都尝试添加相同的密钥,它们将不会并发。

使用了“原子”一词,并且提到这意味着每个键最多应用一次。但没有具体提及该方法的同步行为。

作为旁注,这与我相关,因为由computeIfAbsent调用的方法修改然后使用其主体中的类的字段。*

我想了解两个不同键的computeIfAbsent 方法的两个不同线程执行是否会导致线程问题。

本质上,我必须考虑同步对字段变量的访问及其在我调用的computeIfAbsent 方法中的后续使用。

*( 调用的computeIfAbsent方法是修改字段的唯一方法。除了来自哈希映射computeIfAbsent方法的调用之外,没有该方法的其他调用者。只有一个调用computeWithAbsent方法的并发哈希映射实例调用有问题的“原子”方法)

我的字段是不稳定的,以避免原子可见性的潜在问题。

Dun*_*ncG 2

在某些情况下,映射函数可以针对不同的键值同时执行,因此映射函数是线程安全的非常重要。

computeIfAbsent方法仅保证不会针对同一键值同时调用映射函数。另请注意, aMap的工作原理是将多个键散列到条目存储桶中,如果与映射到 的同一子表的一对键 a+bcomputeIfAbsent(a, mapFunc)同时调用,则每个键将一个接一个地运行,而不是在同一时间。computeIfAbsent(b, mapFunc)ConcurrentHashMapmapFunc

但是,如果不同的键不能解析为同一子表,ConcurrentHashMap您应该期望不同的线程针对不同的键值同时调用您的映射函数。

下面是一个示例,显示了检测并发调用者的线程安全映射函数:

public static void main(String[] args) throws InterruptedException {
    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(2096, 1.0f);
    AtomicInteger concurrent = new AtomicInteger();
    Function<String, String> mappingFunction = s -> {
        int c = concurrent.incrementAndGet();
        String value = "Value:"+s +" concurrent="+c+" thread="+Thread.currentThread().getName();
        if (c != 1)
            System.out.println("Multiple callers for "+value);
        try { Thread.sleep(50); } catch (InterruptedException ignore) { }
        concurrent.decrementAndGet();
        return value;
    };
    Runnable task = () -> {
        Random r = new Random();
        for (int i = 0; i < 10_000; i++)
            map.computeIfAbsent(String.valueOf(r.nextInt(10240)), mappingFunction);
    };

    Thread a = new Thread(task, "one");

    a.start();
    task.run();
    a.join();
    map.values().stream().limit(32).forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)

如果运行得足够多,有时内部计数器会mappingFunction显示两个实例同时在一对线程上运行。

编辑

回答您的评论synchronized (r)

while请注意,内部有无限循环,仅在或computeIfAbsent处退出,并在两个地方调用:breakreturnmappingFunction.apply(key)

  1. 当键是子表中的第一个条目时,它运行到块synchronized (r)。正如前面的一行声明的那样,不同线程Node<K,V> r = new ReservationNode<K,V>()永远不会发生争用,但只有一个线程成功进入块并返回,其他失败的线程将恢复循环。rif (casTabAt(...)) { binCount = 1; ... }

  2. 当键不是子表中的第一个条目时,它会运行到该synchronized (f)块,该块将阻止除一个线程之外的所有线程尝试computeIfAbsent哈希到同一子表的不同键。当每个线程进入块时,它会验证f是否未更改,如果是,则返回现有的或计算出的新值 - 否则恢复循环。