在某些情况下,可以针对不同的键值并发执行映射函数,因此重要的是映射函数是线程安全的.
computeIfAbsent
方法只保证映射函数不会同时调用相同的键值.还要注意,A Map
通过将多个关键字散列到条目桶中来工作,并且如果computeIfAbsent(a, mapFunc)
与computeIfAbsent(b, mapFunc)
同时被调用,并且具有映射到相同子表ConcurrentHashMap
的关键字A+B对,则每个关键字的mapFunc
将被一个接一个地运行,而不是同时运行.
然而,如果不同的键没有解析到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);
}
如果运行足够长,则会出现mappingFunction
内的计数器显示两个实例同时在这对线程上运行的情况.
EDIT个
要回答你对synchronized (r)
条的 comments :
请注意,computeIfAbsent
内部有一个无限的while
循环,它只在break
或return
上退出,mappingFunction.apply(key)
在两个位置被调用:
当密钥是子表中的第一个条目时,它运行到synchronized (r)
块.正如前面的Node<K,V> r = new ReservationNode<K,V>()
行声明的那样,r
上永远不会有来自不同线程的争用,但只有一个线程成功进入if (casTabAt(...)) { binCount = 1; ... }
块并返回,其他失败的线程继续循环.
当关键字不是子表中的第一个条目时,它运行到synchronized (f)
块,这将阻止除一个线程之外的所有线程try computeIfAbsent
以获取散列到相同子表的不同关键字.当每个线程进入该块时,它验证f
是未改变的,并且如果是,则返回现有的或计算出的新值,否则继续循环.