Java并发HashMap.computeIP当前值修改可见性



假设我有一个以集合为值的并发映射:

Map<Integer, List<Integer> map = new ConcurrentHashMap<>();
map.putIfAbsent(8, new ArrayList<>());

我更新值如下:

map.computeIfPresent(8, (i, c) -> {
c.add(5);
return c;
});

我知道computeIfPresent整个方法调用是原子执行的。然而,考虑到这个映射是由多个线程同时访问的,我有点担心对底层集合所做修改的数据可见性。在这种情况下,调用map.get后,值5会出现在列表中吗

我的问题是,如果在computeIfPresent方法调用中执行更改,那么在调用map.get时,列表的更改是否会在其他线程中可见。

请注意,我知道,如果我在执行更新操作之前参考该列表,则对该列表的更改将不可见。如果在更新操作后引用列表(通过调用map.get),我不确定对列表的更改是否可见。

我不确定如何解释文档,但在我看来,在这种特殊情况下,在关系之前发生的事情将保证对基础集合的更改的可见性

更正式地说,给定密钥的更新操作与报告更新值的该密钥的任何(非null)检索具有先发生后发生的关系

该方法被记录为atomic这一事实对visibility没有多大意义(除非这是文档的一部分)。例如,为了简化这一点:

// some shared data
private List<Integer> list = new ArrayList<>();
public synchronized void addToList(List<Integer> x){
list.addAll(x);
}
public /* no synchronized */ List<Integer> getList(){
return list;
}

我们可以说addToList确实是原子的,一次只有一个线程可以调用它。但一旦某个线程调用getList,就无法保证visibility(因为要建立它,它必须发生在同一个锁上)。所以可见性是以前发生过的事情,computeIfPresent文档根本没有说明这一点。

相反,课堂文件上写着:

检索操作(包括get)通常不阻塞,因此可能与更新操作(包括put和remove)重叠

这里的关键点显然是重叠,所以其他一些调用get的线程(从而获得该List)可以看到List处于某种状态;不一定是computeIfPresent启动的状态(在实际调用get之前)。请务必进一步阅读,以了解某些的实际含义。

现在来看文档中最棘手的部分:

Retrievals反映了最近完成的更新操作在开始时保持的结果。更正式地说,给定密钥的更新操作在关系之前发生,该密钥的任何(非null)检索都会报告更新值。

再读一次关于已完成的句子,它说的是,当线程执行get时,你唯一能读到的是List所处的最后一次完成前发生了

想想看,happens-before是在两个后续操作之间建立的(就像上面的同步示例中一样);所以在内部,当您更新Key时,可能会有一个不稳定的书面信号表明更新已经完成(我很确定不是这样做的,只是一个例子)。为了让之前发生的事情真正起作用,get必须读取volatile并查看写入它的状态;如果它看到该状态,则意味着发生在建立之前;我想,通过其他一些技术,这实际上是强制性的。

因此,为了回答您的问题,所有调用get的线程都将看到该密钥上发生的last completed action;在你的情况下,如果你能保证订单,我会说,是的,他们会被看到的。

c.add(5)不是线程安全的,c的内部状态不受映射保护。

使单个值和插入-使用-移除组合线程安全和无竞争条件的确切方法取决于使用模式(同步包装器、写时复制、无锁队列等)。

要澄清您的问题:

您正在提供一些外部担保,以便在Map.get()之前调用Map.computeIfPresent()

您还没有说明您是如何做到这一点的,但假设您是通过使用JVM提供的语义之前发生的来实现的。如果是这种情况,则仅通过关联就可以保证List.add()对调用Map.get()的线程可见,关联发生在关系之前。

现在来回答您实际提出的问题:正如您所指出的,在更新操作ConcurrentHashMap.computeIfPresent()和访问方法ConcurrentMap.get()的后续调用之间,发生在之前。自然地,在List.add()ConcurrentHashMap.computeIfPresent()的末尾之间的关系之前存在

综合起来,答案是

有一个保证,另一个线程将在通过Map.get()获得的List中看到5提供您保证的Map.get()实际上在computeIfPresent()结束后被调用(如问题中所述)。如果后一个保证失效,并且在computeIfPresent()结束之前以某种方式调用了Map.get(),则无法保证其他线程将看到什么,因为ArrayList不是线程安全的。

最新更新