为了账号安全,请及时绑定邮箱和手机立即绑定

Java ConcurrentHashMap.computeIfPresent 值修改可见性

Java ConcurrentHashMap.computeIfPresent 值修改可见性

尚方宝剑之说 2021-10-20 11:44:17
假设我有一个以集合为值的并发映射:Map<Integer, List<Integer> map = new ConcurrentHashMap<>();map.putIfAbsent(8, new ArrayList<>());我更新值如下:map.computeIfPresent(8, (i, c) -> {    c.add(5);    return c;});我知道computeIfPresent整个方法调用都是以原子方式执行的。但是,考虑到该映射由多个线程同时访问,我有点担心对基础集合所做的修改的数据可见性。在这种情况下,调用后将在列表中看到值 5map.get我的问题是,map.get如果在computeIfPresent方法调用中执行更改,则在调用时将更改为在其他线程中可见的列表。请注意,我知道如果我在执行更新操作之前参考列表,对列表的更改将不可见。如果我map.get在更新操作后参考列表(通过调用),我不确定对列表所做的更改是否可见。我不确定如何解释文档,但在我看来,happens-before 关系将保证在这种特殊情况下对基础集合的更改的可见性更正式地,给定键的更新操作与报告更新值的该键的任何(非空)检索具有先发生关系
查看完整描述

3 回答

?
叮当猫咪

TA贡献1776条经验 获得超12个赞

澄清你的问题:

你提供一些对外担保这种Map.computeIfPresent()称为前 Map.get()

您还没有说明您是如何做到这一点的,但假设您是通过使用JVM 提供的具有发生前语义的东西来做到这一点的。如果是这种情况,那么,仅仅保证List.add()是可见的线程调用Map.get()只需协会之前发生关系。

现在回答您实际提出的问题:正如您所指出的,更新操作与随后调用 access 方法之间存在发生在之前的关系。很自然地,在和 的结尾之间有一个happens-before关系。ConcurrentHashMap.computeIfPresent()ConcurrentMap.get()List.add()ConcurrentHashMap.computeIfPresent()

综合起来,答案是肯定的

有一个保证其他线程将看到5List通过获取Map.get()提供你担保Map.get()实际上是后调用 computeIfPresent()端(如问题所述)。如果后一个保证出错并且Map.get()computeIfPresent()结束前以某种方式被调用,则无法保证其他线程将看到什么,因为ArrayList它不是线程安全的。


查看完整回答
反对 回复 2021-10-20
?
慕勒3428872

TA贡献1848条经验 获得超6个赞

事实上,该方法被记录为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文档根本没有对此进行任何说明。


相反,类文档说:


检索操作(包括获取)一般不会阻塞,因此可能与更新操作(包括放置和删除)重叠。


这里的关键点显然是overlap,所以其他一些线程调用get(从而获得了那个List),可以List在某种状态下看到它;不一定是computeIfPresent开始的状态(在您实际调用之前get)。请务必进一步阅读以了解某些实际可能意味着什么。


现在到该文档中最棘手的部分:


检索反映了最近完成的更新操作的结果。更正式地,给定键的更新操作与报告更新值的该键的任何(非空)检索具有发生前关系。


再读一遍关于完成的句子,它说的是当一个线程执行时你唯一能读到的get是List所处的最后一个完成状态。现在下一句话说在两个动作之间建立之前发生了。


想一想,ahappens-before是在两个后续动作之间建立的(就像上面的synchronized例子);因此,当您在内部更新 a 时Key,可能会有一个不稳定的书面信号表明更新已完成(我很确定它不是以这种方式完成的,只是一个例子)。对于在实际工作之前发生的事情,get必须读取 volatile 并查看写入它的状态;如果它看到那个状态,则意味着之前发生的事情已经建立;我猜想通过其他一些技术,这实际上是强制执行的。


所以要回答你的问题,所有调用的线程get都会看到last completed action那个键上发生的事情;在您的情况下,如果您可以保证该订单,我会说,是的,它们将是可见的。


查看完整回答
反对 回复 2021-10-20
  • 3 回答
  • 0 关注
  • 354 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信