比方说,我有一个以集合为值的并发映射:
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
我的问题是,如果在map.get
方法调用内进行了更改,则在调用computeIfPresent
时,列表将在其他线程中可见。
请注意,我知道如果在执行更新操作之前引用列表,将看不到列表的更改。我不确定如果在更新操作后引用了列表(通过调用map.get
)来引用列表,该列表的更改是否可见。
我不确定如何解释文档,但在我看来,在这种特殊情况下,事前发生的关系将保证对底层集合所做更改的可见性
更正式地说,给定键的更新操作与该键报告更新值的任何(非空)检索具有先发生后关联的关系
答案 0 :(得分:4)
该方法记录为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
(因为要建立it has to happens on the same lock)。因此,可见性是在关注之前发生的事情,computeIfPresent
文档对此没有任何说明。
相反,类文档说:
检索操作(包括获取)通常不会阻塞,因此可能与更新操作(包括放置和删除)发生重叠。
这里的关键点显然是 overlap ,因此其他一些调用get
的线程(因此获得了List
的持有权)可以看到List
在某种状态下不一定是computeIfPresent
开始的状态(在您实际呼叫get
之前)。请务必进一步阅读以了解 some 的实际含义。
现在到该文档中最棘手的部分:
检索反映了最近发生的已完成更新操作的结果。更正式地说,给定键的更新操作与 happens-before 关系与该键的任何(非空)检索报告更新值。
再次阅读有关 completed 的那句话,它的意思是,当线程执行get
时,您唯一能读到的是最后完成的状态List在里面。现在下一个句子说在两个动作之间建立了之前。
考虑一下,在两个随后的动作之间建立了一个happens-before
(例如上面的同步示例);因此,在内部,当您更新Key
时,可能会出现易失的书面信号,表明更新已完成(我很确定它不是以这种方式完成的,只是一个例子)。为了在实际工作之前发生这种情况,get
必须读取该易失性并查看写入其中的状态。如果看到该状态,则表示发生在之前;而且我猜想是通过其他一些方法实际上可以实施的。
因此,为回答您的问题,所有调用get
的线程都将看到该键上发生的last completed action
;在您的情况下,如果您可以保证该订单,我会说,是的,它们将可见。
答案 1 :(得分:2)
c.add(5)
不是线程安全的,c
的内部状态不受映射的保护。
使单个值和插入/删除组合线程安全且免于竞争条件的确切方法取决于使用模式(同步包装,写时复制,无锁队列等)。
答案 2 :(得分:1)
您要提供一些外部保证,以使Map.computeIfPresent()
先被调用 Map.get()
。
您尚未说明如何执行此操作,但可以说您是通过使用JVM提供的具有 happens-before 语义的功能来执行此操作的。如果是这种情况,那么仅通过 happens-before的 association 单独保证List.add()
对于调用Map.get()
的线程是可见的关系。
现在要回答您实际上要问的问题:如前所述,更新操作ConcurrentHashMap.computeIfPresent()
与访问方法{的后续调用之间存在ConcurrentMap.get()
和List.add()
的末尾之间存在先发生关系。
放在一起,答案是是。
可以保证其他线程在通过ConcurrentHashMap.computeIfPresent()
获得的5
中看到List
,已提供,您保证Map.get()
实际上是在之后Map.get()
结束时调用(如问题所述)。如果后一个保证无效,并且在computeIfPresent()
结束之前以某种方式调用了Map.get()
,则由于computeIfPresent()
不是线程安全的,因此无法保证其他线程会看到什么。