假设我有一个以集合为值的并发映射:
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
不是线程安全的。