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

Java 如何保证ConcurrentHashMap的get()始终返回最新的实际值?,java,multithreading,concurrency,volatile,concurrenthashmap,Java,Multithreading,Concurrency,Volatile,Concurrenthashmap,简介 假设我有一个ConcurrentHashMap单例: public class RecordsMapSingleton { private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>(); public static ConcurrentHashMap<String, Record> getInstance() {

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

public class RecordsMapSingleton {

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

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

}
然后,在每次修改
Map
Record
内部后,我将调用
incrementRevision()
,在从Map读取之前,我将调用
getRevision()


问题
由于海森堡的性质,任何数量的测试都不足以证明该解决方案是正确的。我不是并发方面的专家,所以无法正式验证它。


有人会同意,遵循这种方法可以保证我总是从
ConcurrentHashMap
获取最新的实际值吗?如果这种方法不正确或效率低下,您能给我推荐其他方法吗?

您的方法不起作用,因为您实际上又在重复同样的错误。As
ConcurrentHashMap.put
ConcurrentHashMap.get
将创建一个“发生之前”关系,但没有时间顺序保证,这同样适用于对
volatile
变量的读写。它们形成了一个before-before关系,但没有时间顺序保证,如果一个线程恰好在执行另一个线程之前调用
get
,那么同样的情况也会发生在
volatile
写入之前的
volatile
读取。除此之外,您还添加了另一个错误,因为将
++
运算符应用于
volatile
变量不是原子变量

volatile
变量的保证并不比对
ConcurrentHashMap
的保证更有力。明确指出:

检索反映了最近完成的更新操作在开始时的结果

外部操作是关于以下各项的线程间操作:

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

  • 外部行动。外部操作是可以在执行外部观察到的操作,并且其结果基于执行外部的环境
简单地说,如果一个线程放入一个
ConcurrentHashMap
并向一个外部实体发送一条消息,而第二个线程在根据之前发送的消息从一个外部实体接收到一条消息后从同一个
ConcurrentHashMap
获取消息,那么就不会有内存可见性问题


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

也许我在这里读错了你的问题,但是你似乎认为读总是在写之后发生;为什么?@fge虽然是独立线程。这是一个预定的序列(put-get-get),我很感兴趣,有时会出错;你不能保证线程的执行顺序,除非你互相保护它们,所以这看起来很像一个失败的原因;您所称的“最新值”是最后一个写入线程所写入的值,因此到目前为止是一致的。就我所见,您需要一些其他的同步机制。@fge好吧,这第二个服务只有在我向他发送ID之后才会向我发送第二个请求。只有在我向他发送对第二个请求的响应之后才会发送第三个请求。我认为这个序列是由于流的逻辑而被迫的。第二个服务在收到来自我的包含ID的请求之前无法发送第二个请求,在收到对第二个请求的响应之前无法发送第三个请求。@mkrakhin如果有多个实例以某种方式负载平衡,我确信这是您的问题。获取单例的内存引用可能有助于调试和确认这一点。您的观点很有趣。我认为volatile修饰符提供了比ConcurrentHashMap方法(您可以在对问题的评论中看到我对它的看法)更多的其他类型的保证(有点像,如果读取是在写入之后,它将看到正确的值)。顺便说一句,感谢您提醒,增量不是原子的,这是我的疏忽。另外要添加的是,JavaDoc for ConcurrentHashMap禁止将null作为键或值插入-这证明您要查找的键在映射中不存在。如果读取是“真的在写入之后”,您将看到正确的值,但这同样适用于
ConcurrentMap
get
put
。请注意,如果您的代码是根据您所描述的意图安排的,即与外部实体的通信意味着排序,那么您用于通信的API确实会为您提供扩展到
ConcurrentMap
操作的排序。但是,由于您遇到了问题,代码显然不是这样安排的(除非真正的错误在于打印或不相关的区域…@Holger,正如我前面所说的,通信实际上意味着排序。请求是无条件的后续请求。所以问题似乎真的存在于其他领域。您能否提供一些规范中的引用,证明ConcurrentHashMap确实提供了这种保证,所以我可以接受您的回答?Oracle的Aleksey Shipilev确认,ConcurrentHashMap确实应该在检索时返回最新更新。我接受这个答案。
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;
    }

}