【发布时间】:2020-10-07 17:02:16
【问题描述】:
我看到一些奇怪的 NPE,并发现这是一个并发问题。我正在使用类似于以下的代码:
class Manager {
private final ConcurrentMap<String, Value> map = new ConcurrentHashMap<>();
public Value get(String key) {
Value v = map.get(key);
if (v == null) {
new Value(key, this);
v = map.get(key);
}
return v;
}
public void doIt(String key) {
get(key).doIt();
}
void register(String key, Value v) {
map.put(key, v);
}
}
class Value {
private final Value[] values;
private final SubValue v;
Value(String key, Manager m) {
m.register(key, this);
// Initialize some values, this is where a cycle can be introduced
// This is just some sample code, the gist is, we call Manager.get
this.vs = new Value[]{ m.get("some-other-key") };
// Other code ...
this.v = new SubValue(m);
}
public void doIt() {
this.v.doIt(); // <--- NPE here. v is null sometimes
}
}
当我调用 Manager.doIt 时,有时我会收到 NPE,因为 Value.v 是 null。据我了解之前发生的关系,当Manager.get 被同时调用并且还没有键的条目时,我可以取回尚未完全初始化的值。
我在Value 的构造函数中注册对象,因为Value 对象之间的对象图可以有循环,如果没有这个,我会得到一个stackoverflow 异常。
现在的问题是,我如何确保在 doIt 中 Value 和所有连接的值都已完全初始化?我正在考虑在Manager.get 中进行某种双重检查锁定,但我不确定如何最好地解决这个问题。像这样的:
public Value get(String key) {
Value v = map.get(key);
if (v == null) {
synchronized(map) {
v = map.get(key);
if (v == null) {
v = new Value(key, this);
}
}
}
return v;
}
有没有人知道如何解决这个问题或发现该代码存在并发问题?
【问题讨论】:
-
虽然我还在弄清楚程序的其余部分,但双重检查锁定本身就存在内存可见性问题。
-
这里的问题不是内存可见性。在 V 的构造函数中,你甚至在对象被正确构造之前就使这个引用转义了。
-
附带说明,您的整个 get 方法无论如何都不是线程安全的。查看
computeIfAbsent并仔细阅读它的文档。
标签: java concurrency jvm java-memory-model