繁体   English   中英

如何保证ConcurrentHashMap的get()始终返回最新的实际值?

[英]How to guarantee get() of ConcurrentHashMap to always return the latest actual value?

介绍
假设我有一个ConcurrentHashMap单例:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

}

然后,我有来自不同来源的三个后续请求(均由不同的线程处理)。
第一个服务发出一个请求,该请求获取单例,创建Record实例,生成唯一ID并将其放入Map ,然后将此ID发送给另一个服务。
然后,第二个服务使用该ID发出另一个请求。 它获取单例,找到Record实例并对其进行修改。
最后(可能是半小时后),第二个服务再次发出请求,以进一步修改Record

问题
在某些非常罕见的情况下,我遇到了heisenbug 在日志中,我可以看到,第一个请求成功将Record放入Map ,第二个请求按ID找到并修改了它,然后第三个请求尝试按ID查找Record,但未找到任何内容( get()返回null )。
我发现有关ConcurrentHashMap的唯一保证是:

在将对象放入任何并发集合之前,线程中的操作发生在访问另一个线程中的元素或从集合中删除该元素之后的操作。

这里 如果我做对了,从字面上看,它意味着get()可以返回某个时候实际上在Map中的任何值,只要它不会破坏happens-before在不同线程之间的动作之间的关系即可。
在我的情况下,它的用法是这样的:如果第三个请求不关心第一个和第二个处理期间发生了什么,那么它可以从Map读取null

它不适合我,因为我真的需要从Map获取最新的实际Record

我尝试了什么
因此我开始思考,如何在后续Map修改之间建立关系happens-before 并产生了想法。 JLS (在17.4.4)认为:

对易失性变量v的写操作(第8.3.1.4节)与任何线程对v的所有后续读取进行同步(其中“后续”是根据同步顺序定义的)。

因此,让我们假设,我将像这样修改我的单身人士:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
    private static volatile long revision = 0;

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

    public static void incrementRevision() {
        revision++;
    }
    public static long getRevision() {
        return revision;
    }

}

然后,每次修改后MapRecord里面,我会打电话给incrementRevision()并从地图我任何读前会打电话给getRevision()


由于heisenbugs的性质,无法进行大量测试就足以证明该解决方案是正确的。 而且我不是并发方面的专家,因此无法正式验证它。

有人可以认可,采用这种方法可以确保我始终从ConcurrentHashMap获取最新的实际值吗? 如果这种方法不正确或效率低下,您可以推荐我其他方法吗?

您的方法将不起作用,因为您实际上是在重复相同的错误。 由于ConcurrentHashMap.putConcurrentHashMap.get将在发生关系之前创建事件,但没有时间顺序保证,因此对volatile变量的读取和写入完全相同。 它们关系发生之前发生,但是没有时间顺序保证,如果一个线程碰巧在另一个执行put之前调用get ,则该情况同样适用于在volatile写入之前发生的volatile读取。 除此之外,您还添加了另一个错误,因为将++运算符应用于volatile变量不是原子的。

volatile变量所做的保证并不比对ConcurrentHashMap所作的保证强。 它的文档明确指出:

检索反映了自发生以来最新完成的更新操作的结果。

JLS声明外部动作是与程序顺序有关的线程间动作:

线程间操作是由一个线程执行的操作,可以被另一个线程检测或直接影响该操作。 程序可以执行几种类型的线程间操作:

  • 外部行动 外部动作是在执行外部可以观察到的动作,其结果基于执行外部的环境。

简而言之,如果一个线程放入ConcurrentHashMap并向外部实体发送消息,而第二个线程在从外部实体接收到一条消息(取决于先前发送的消息)后从同一个ConcurrentHashMap获取消息,则无法看到内存问题。

可能是因为这些操作未采用这种方式进行编程,或者外部实体没有假定的依赖关系,但也可能是错误位于完全不同的区域,但我们无法如您所愿没有发布相关代码,例如密钥不匹配或打印代码错误。 但是不管它是什么,它都不会由volatile变量解决。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM