[英]How to guarantee get() of ConcurrentHashMap to always return the latest actual value?
Introduction 介绍
Suppose I have a ConcurrentHashMap singleton: 假设我有一个ConcurrentHashMap单例:
public class RecordsMapSingleton {
private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, Record> getInstance() {
return payments;
}
}
Then I have three subsequent requests (all processed by different threads) from different sources. 然后,我有来自不同来源的三个后续请求(均由不同的线程处理)。
The first service makes a request, that gets the singleton, creates Record
instance, generates unique ID and places it into Map
, then sends this ID to another service. 第一个服务发出一个请求,该请求获取单例,创建
Record
实例,生成唯一ID并将其放入Map
,然后将此ID发送给另一个服务。
Then the second service makes another request, with that ID. 然后,第二个服务使用该ID发出另一个请求。 It gets the singleton, finds
Record
instance and modifies it. 它获取单例,找到
Record
实例并对其进行修改。
Finally (probably after half an hour) the second service makes another request, in order to modify Record
further. 最后(可能是半小时后),第二个服务再次发出请求,以进一步修改
Record
。
Problem 问题
In some really rare cases, I'm experiencing heisenbug . 在某些非常罕见的情况下,我遇到了heisenbug 。 In logs I can see, that first request successfully placed
Record
into Map
, second request found it by ID and modified it, and then third request tried to find Record by ID, but found nothing ( get()
returned null
). 在日志中,我可以看到,第一个请求成功将
Record
放入Map
,第二个请求按ID找到并修改了它,然后第三个请求尝试按ID查找Record,但未找到任何内容( get()
返回null
)。
The single thing that I found about ConcurrentHashMap
guarantees, is: 我发现有关
ConcurrentHashMap
的唯一保证是:
Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.
在将对象放入任何并发集合之前,线程中的操作发生在访问另一个线程中的元素或从集合中删除该元素之后的操作。
from here . 从这里 。 If I got it right, it literally means, that
get()
could return any value that actually was sometime into Map, as far as it doesn't ruin happens-before
relationship between actions in different threads. 如果我做对了,从字面上看,它意味着
get()
可以返回某个时候实际上在Map中的任何值,只要它不会破坏happens-before
在不同线程之间的动作之间的关系即可。
In my case it applies like this: if third request doesn't care about what happened during processing of first and second, then it could read null
from Map
. 在我的情况下,它的用法是这样的:如果第三个请求不关心第一个和第二个处理期间发生了什么,那么它可以从
Map
读取null
。
It doesn't suit me, because I really need to get from Map
the latest actual Record
. 它不适合我,因为我真的需要从
Map
获取最新的实际Record
。
What have I tried 我尝试了什么
So I started to think, how to form happens-before
relationship between subsequent Map
modifications; 因此我开始思考,如何在后续
Map
修改之间建立关系happens-before
; and came with idea. 并产生了想法。 JLS says (in 17.4.4) that:
JLS 说 (在17.4.4)认为:
A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).
对易失性变量v的写操作(第8.3.1.4节)与任何线程对v的所有后续读取进行同步(其中“后续”是根据同步顺序定义的)。
So, let's suppose, I'll modify my singleton like this: 因此,让我们假设,我将像这样修改我的单身人士:
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;
}
}
Then, after each modification of Map
or Record
inside, I'll call incrementRevision()
and before any read from Map I'll call getRevision()
. 然后,每次修改后
Map
或Record
里面,我会打电话给incrementRevision()
并从地图我任何读前会打电话给getRevision()
Question 题
Due to nature of heisenbugs no amount of tests is enough to tell that this solution is correct. 由于heisenbugs的性质,无法进行大量测试就足以证明该解决方案是正确的。 And I'm not an expert in concurrency, so couldn't verify it formally.
而且我不是并发方面的专家,因此无法正式验证它。
Can someone approve, that following this approach guarantees that I'm always going to get the latest actual value from ConcurrentHashMap
? 有人可以认可,采用这种方法可以确保我始终从
ConcurrentHashMap
获取最新的实际值吗? If this approach is incorrect or appears to be inefficient, could you recommend me something else? 如果这种方法不正确或效率低下,您可以推荐我其他方法吗?
You approach will not work as you are actually repeating the same mistake again. 您的方法将不起作用,因为您实际上是在重复相同的错误。 As
ConcurrentHashMap.put
and ConcurrentHashMap.get
will create a happens before relationship but no time ordering guaranty, exactly the same applies to your reads and writes to the volatile
variable. 由于
ConcurrentHashMap.put
和ConcurrentHashMap.get
将在发生关系之前创建事件,但没有时间顺序保证,因此对volatile
变量的读取和写入完全相同。 They form a happens before relationship but no time ordering guaranty, if one thread happens to call get
before the other performed put
, the same applies to the volatile
read that will happen before the volatile
write then. 它们在关系发生之前发生,但是没有时间顺序保证,如果一个线程碰巧在另一个执行
put
之前调用get
,则该情况同样适用于在volatile
写入之前发生的volatile
读取。 Besides that, you are adding another error as applying the ++
operator to a volatile
variable is not atomic. 除此之外,您还添加了另一个错误,因为将
++
运算符应用于volatile
变量不是原子的。
The guarantees made for volatile
variables are not stronger than these made for a ConcurrentHashMap
. 对
volatile
变量所做的保证并不比对ConcurrentHashMap
所作的保证强。 It's documentation explicitly states: 它的文档明确指出:
Retrievals reflect the results of the most recently completed update operations holding upon their onset.
检索反映了自发生以来最新完成的更新操作的结果。
The JLS states that external actions are inter-thread actions regarding the program order : JLS声明外部动作是与程序顺序有关的线程间动作:
An inter-thread action is an action performed by one thread that can be detected or directly influenced by another thread.
线程间操作是由一个线程执行的操作,可以被另一个线程检测或直接影响该操作。 There are several kinds of inter-thread action that a program may perform:
程序可以执行几种类型的线程间操作:
…
…
- External Actions .
外部行动 。 An external action is an action that may be observable outside of an execution, and has a result based on an environment external to the execution.
外部动作是在执行外部可以观察到的动作,其结果基于执行外部的环境。
Simply said, if one thread puts into a ConcurrentHashMap
and sends a message to an external entity and a second thread gets from the same ConcurrentHashMap
after receiving a message from an external entity depending on the previously sent message, there can't be a memory visibility issue. 简而言之,如果一个线程放入
ConcurrentHashMap
并向外部实体发送消息,而第二个线程在从外部实体接收到一条消息(取决于先前发送的消息)后从同一个ConcurrentHashMap
获取消息,则无法看到内存问题。
It might be the case that these action aren't programmed that way or that the external entity doesn't have the assumed dependency, but it might be the case that the error lies in a completely different area but we can't tell as you didn't post the relevant code, eg the key doesn't match or the printing code is wrong. 可能是因为这些操作未采用这种方式进行编程,或者外部实体没有假定的依赖关系,但也可能是错误位于完全不同的区域,但我们无法如您所愿没有发布相关代码,例如密钥不匹配或打印代码错误。 But whatever it is, it won't be fixed by the
volatile
variable. 但是不管它是什么,它都不会由
volatile
变量解决。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.