Sha*_*awn 5 java multithreading
我知道周围有很多问题computeIfAbsent。
具体来说,我正在寻找的是理解并发哈希映射的原子性声明。
来自JavaDoc
整个方法调用都是原子执行的,因此每个键最多应用该函数一次。
如果两个线程尝试computeIfAbsent使用不同的键执行,并发现在这两种情况下映射都不包含它们,如果缺少函数,计算结果的执行是否可能是并发的?我知道如果两个线程都尝试添加相同的密钥,它们将不会并发。
使用了“原子”一词,并且提到这意味着每个键最多应用一次。但没有具体提及该方法的同步行为。
作为旁注,这与我相关,因为由computeIfAbsent调用的方法修改然后使用其主体中的类的字段。*
我想了解两个不同键的computeIfAbsent 方法的两个不同线程执行是否会导致线程问题。
本质上,我必须考虑同步对字段变量的访问及其在我调用的computeIfAbsent 方法中的后续使用。
*( 调用的computeIfAbsent方法是修改字段的唯一方法。除了来自哈希映射computeIfAbsent方法的调用之外,没有该方法的其他调用者。只有一个调用computeWithAbsent方法的并发哈希映射实例调用有问题的“原子”方法)
我的字段是不稳定的,以避免原子可见性的潜在问题。
在某些情况下,映射函数可以针对不同的键值同时执行,因此映射函数是线程安全的非常重要。
该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)
当键是子表中的第一个条目时,它运行到块synchronized (r)。正如前面的一行声明的那样,不同线程Node<K,V> r = new ReservationNode<K,V>()永远不会发生争用,但只有一个线程成功进入块并返回,其他失败的线程将恢复循环。rif (casTabAt(...)) { binCount = 1; ... }
当键不是子表中的第一个条目时,它会运行到该synchronized (f)块,该块将阻止除一个线程之外的所有线程尝试computeIfAbsent哈希到同一子表的不同键。当每个线程进入块时,它会验证f是否未更改,如果是,则返回现有的或计算出的新值 - 否则恢复循环。