gpt4 book ai didi

java - 使用双重检查锁定,对 volatile ConcurrentHashMap 的 put 是否有 happens-before 保证?

转载 作者:行者123 更新时间:2023-11-29 03:32:59 25 4
gpt4 key购买 nike

到目前为止,我使用了如下双重检查锁定:

class Example {
static Object o;
volatile static boolean setupDone;

private Example() { /* private constructor */ }

getInstance() {
if(!setupDone) {
synchronized(Example.class) {
if(/*still*/ !setupDone) {
o = new String("typically a more complicated operation");
setupDone = true;
}
}
}
return o;
}
}// end of class

现在,因为我们有共享这个类的线程组,所以我们将 boolean 更改为 ConcurrentHashMap,如下所示:

class Example {
static ConcurrentHashMap<String, Object> o = new ConcurrentHashMap<String, Object>();
static volatile ConcurrentHashMap<String, Boolean> setupsDone = new ConcurrentHashMap<String, Boolean>();

private Example() { /* private constructor */ }

getInstance(String groupId) {
if (!setupsDone.containsKey(groupId)) {
setupsDone.put(groupId, false);
}
if(!setupsDone.get(groupId)) {
synchronized(Example.class) {
if(/*still*/ !setupsDone.get(groupId)) {
o.put(groupId, new String("typically a more complicated operation"));
setupsDone.put(groupId, true); // will this still maintain happens-before?
}
}
}
return o.get(groupId);
}
}// end of class

我现在的问题是:如果我将一个标准的 Object 声明为 volatile,我只会在读取或写入它的引用时建立一个 happens-before 关系。因此,在该对象中写入一个元素(如果它是例如标准的 HashMap,对其执行 put() 操作)将不会建立这样的一段关系。 这对吗?(读取一个元素怎么样;难道不需要读取引用并因此建立关系吗?)

现在,使用 volatile ConcurrentHashMap,将写入一个元素 建立 happens-before 关系,即上述是否仍然有效?

更新:这个问题的原因以及双重检查锁定的重要性:我们实际设置的(而不是对象)是一个 MultiThreadedHttpConnectionManager,我们将一些设置传递给它,然后我们将其传递给我们设置的 HttpClient,也是,我们回来了。我们最多有 10 个组,每个组最多 100 个线程,我们使用双重检查锁定,因为我们不想在它们需要获取其组的 HttpClient 时阻塞每个线程,作为一个整体设置将用于帮助进行性能测试。由于笨拙的设计和奇怪的平台,我们在其上运行它,我们不能只从外部传递对象,所以我们希望以某种方式使这个设置工作。 (我意识到问题的原因有点具体,但我希望问题本身足够有趣:Is there a way to get that ConcurrentHashMap to use "volatile behavior",即在 ConcurrentHashMap 上执行 put() 时,建立一个 happens-before 关系,就像 volatile boolean 值 所做的那样? ;)

最佳答案

是的,这是正确的。 volatile 仅保护该对象引用,不保护其他对象。

不,将元素放入 volatile HashMap 不会创建 happens-before 关系,即使是 ConcurrentHashMap

实际上 ConcurrentHashMap 不为读取操作持有锁(例如 containsKey())。参见 ConcurrentHashMap Javadoc.

更新:

反射(reflect)您更新后的问题:您必须同步您放入 CHM 的对象。我建议使用容器对象,而不是直接将对象存储在 map 中:

public class ObjectContainer {
volatile boolean isSetupDone = false;
Object o;
}

static ConcurrentHashMap<String, ObjectContainer> containers =
new ConcurrentHashMap<String, ObjectContainer>();

public Object getInstance(String groupId) {
ObjectContainer oc = containers.get(groupId);
if (oc == null) {
// it's enough to sync on the map, don't need the whole class
synchronized(containers) {
// double-check not to overwrite the created object
if (!containers.containsKey(groupId))
oc = new ObjectContainer();
containers.put(groupId, oc);
} else {
// if another thread already created, then use that
oc = containers.get(groupId);
}
} // leave the class-level sync block
}

// here we have a valid ObjectContainer, but may not have been initialized

// same doublechecking for object initialization
if(!oc.isSetupDone) {
// now syncing on the ObjectContainer only
synchronized(oc) {
if(!oc.isSetupDone) {
oc.o = new String("typically a more complicated operation"));
oc.isSetupDone = true;
}
}
}
return oc.o;
}

注意,在创建时,最多一个线程可以创建ObjectContainer。但是在初始化时,每个组都可以并行初始化(但每个组最多 1 个线程)。

也可能发生 Thread T1 将创建 ObjectContainer,但 Thread T2 将初始化它。

是的,保留 ConcurrentHashMap 是值得的,因为映射读取和写入将同时发生。但是 volatile 不是必需的,因为 map 对象本身不会改变。

可悲的是,双重检查并不总是有效,因为编译器可能会创建一个字节码,它会重用 containers.get(groupId) 的结果(易变的是SetupDone)。这就是为什么我必须使用 containsKey 进行双重检查的原因。

关于java - 使用双重检查锁定,对 volatile ConcurrentHashMap 的 put 是否有 happens-before 保证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17112504/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com