【发布时间】:2015-06-28 14:07:15
【问题描述】:
简介
假设我有一个 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 查找记录,但什么也没找到(get() 返回@987654332 @)。
我发现关于ConcurrentHashMap 保证的唯一一点是:
在将对象放入任何并发之前线程中的操作 收集发生在访问或删除之后的操作 从另一个线程中的集合中提取该元素。
来自here。如果我做对了,它的字面意思是,get() 可以返回任何实际存在于 Map 中的值,只要它不会破坏 happens-before 不同线程中的操作之间的关系。
在我的情况下,它是这样应用的:如果第三个请求不关心第一个和第二个处理过程中发生的事情,那么它可以从 Map 读取 null。
它不适合我,因为我真的需要从 Map 获取最新的实际 Record。
我尝试了什么
于是我开始思考,如何在后续的Map修改之间形成happens-before关系;并提出了想法。 JLS says(在 17.4.4 中):
对 volatile 变量 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;
}
}
然后,在每次修改 Map 或 Record 内部后,我将调用 incrementRevision(),在从 Map 读取任何内容之前,我将调用 getRevision()。
问题
由于 heisenbugs 的性质,没有多少测试足以证明这个解决方案是正确的。而且我不是并发方面的专家,所以无法正式验证它。
有人可以批准,遵循这种方法可以保证我总是能从ConcurrentHashMap 获得最新的实际值吗?如果这种方法不正确或看起来效率低下,您能推荐我其他方法吗?
【问题讨论】:
-
也许我在这里读错了你的问题,但你似乎认为读总是在写之后发生;为什么?
-
@fge 尽管它们是独立的线程。这是一个预先确定的顺序(put-get-get),我很感兴趣,但有时会出错。
-
好吧,无论如何它会在某一点或另一点出错;你不能保证线程的执行顺序,除非你互相保护它们,所以这看起来很像一个失败的原因;您所说的“最新值”是最后一个写入线程将写入的任何值,因此到目前为止它是一致的。据我所知,您需要一些其他同步机制。
-
@fge 好吧,这第二个服务只有在我向他发送 ID 后才会向我发送第二个请求。并且仅在我向他发送对第二个请求的响应后才发送第三个请求。我认为由于流程的逻辑,这个顺序是强制的。第二个服务在收到我的包含 ID 的请求之前无法发送第二个请求,并且在收到对第二个的响应之前它无法发送第三个请求。
-
@mkrakhin 如果有多个实例以某种方式负载平衡,我相信这是您的问题。获取单例的内存引用可能有助于您的调试和确认。
标签: java multithreading concurrency volatile concurrenthashmap