Java 使用双重检查锁定时,在保证之前是否对易失性ConcurrentHashMap执行put操作?
到目前为止,我使用了双重检查锁定,如下所示:Java 使用双重检查锁定时,在保证之前是否对易失性ConcurrentHashMap执行put操作?,java,multithreading,volatile,concurrenthashmap,happens-before,Java,Multithreading,Volatile,Concurrenthashmap,Happens Before,到目前为止,我使用了双重检查锁定,如下所示: class Example { static Object o; volatile static boolean setupDone; private Example() { /* private constructor */ } getInstance() { if(!setupDone) { synchronized(Example.class) { if(/*still*/ !setupD
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
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
现在,由于我们有共享该类的线程组,我们将布尔值更改为ConcurrentHashMap,如下所示:
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
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
我现在的问题是:如果我将一个标准对象声明为volatile,那么当我读或写它的引用时,我只会得到一个before关系。因此,如果在该对象中写入元素(例如,标准HashMap),则对其执行put操作不会建立这种关系。对吗?读一个元素怎么样;这难道不需要阅读参考资料,从而建立关系吗
现在,通过使用volatile ConcurrentHashMap,向其中写入一个元素是否会建立“发生在之前”关系,即上述关系是否仍然有效
更新:出现此问题的原因以及双重检查锁定的重要性:
我们实际上设置的不是对象,而是多线程HttpConnectionManager,我们向它传递一些设置,然后将其传递到HttpClient,我们也设置了它,然后返回它。我们最多有10个组,每个组最多有100个线程,我们使用双重检查锁定,因为我们不希望在他们需要获取组的HttpClient时阻止每个线程,因为整个设置将用于帮助进行性能测试。由于一个笨拙的设计和一个奇怪的平台,我们不能仅仅从外部传递对象,所以我们希望以某种方式使这个设置工作。我意识到这个问题的原因有点具体,但我希望这个问题本身足够有趣:有没有办法让ConcurrentHashMap使用volatile行为,即在ConcurrentHashMap上执行put时,建立一种发生在之前的关系,就像volatile布尔值那样 是的,这是正确的。volatile只保护该对象引用,而不保护其他对象 不,将元素放入volatile HashMap不会创建“发生在之前”关系,即使使用ConcurrentHashMap也不会 实际上,ConcurrentHashMap并没有为读取操作(例如containsKey)持有锁。见Javadoc 更新: 反映您更新的问题:您必须对放入CHM的对象进行同步。我建议使用容器对象,而不是直接将对象存储在地图中:
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个线程
线程T1也可能会创建ObjectContainer,但线程T2会初始化它
是的,保留ConcurrentHashMap是值得的,因为映射读取和写入将同时发生。但是volatile不是必需的,因为map对象本身不会改变
可悲的是,双重检查并不总是有效的,因为编译器可能会创建一个字节码来重用containers.getgroupId的结果,而volatile isSetupDone的情况并非如此。这就是为什么我必须使用containsKey进行双重检查。是的,它是正确的。volatile只保护该对象引用,而不保护其他对象 不,将元素放入volatile HashMap不会创建“发生在之前”关系,即使使用ConcurrentHashMap也不会 实际上,ConcurrentHashMap并没有为读取操作(例如containsKey)持有锁。见Javadoc 更新: 反映您更新的问题:您必须对放入CHM的对象进行同步。我建议使用容器对象,而不是直接将对象存储在地图中:
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个线程
线程T1也可能会创建ObjectContainer,但线程T2会初始化它
是的,保留ConcurrentHashMap是值得的,因为映射读取和写入将同时发生。但是volatile不是必需的,因为map对象本身不会改变
可悲的是,双重检查并不总是有效的,因为编译器可能会创建一个字节码来重用containers.getgroupId的结果,而volatile isSetupDone的情况并非如此。这就是为什么我必须使用containsKey进行双重检查
因此,如果在该对象中写入元素(例如,标准HashMap),则对其执行put操作不会建立这种关系。对吗
是和否。读取或写入易变字段时,始终存在“发生在”关系。本例中的问题是,即使在访问HashMap字段之前发生了错误,但在实际操作HashMap时,没有内存同步或互斥锁。因此,多个线程可以创建多个线程
ee同一HashMap的不同版本,并可能根据竞争条件创建损坏的数据结构
现在,通过使用volatile ConcurrentHashMap,向其中写入一个元素是否会建立“发生在之前”关系,即上述关系是否仍然有效
通常,您不需要将ConcurrentHashMap标记为易失性。ConcurrentHashMap代码本身内部存在跨越的内存障碍。如果ConcurrentHashMap字段经常被更改,那么我唯一会使用它,即它是非最终字段
你的代码看起来真的像是过早的优化。探查器是否向您表明这是一个性能问题?我建议你在地图上同步,我就完成了。有两个ConcurrentHashMap来解决这个问题对我来说似乎太过分了
因此,如果在该对象中写入元素(例如,标准HashMap),则对其执行put操作不会建立这种关系。对吗
是和否。读取或写入易变字段时,始终存在“发生在”关系。本例中的问题是,即使在访问HashMap字段之前发生了错误,但在实际操作HashMap时,没有内存同步或互斥锁。因此,多个线程可以看到同一HashMap的不同版本,并且可以根据竞争条件创建损坏的数据结构
现在,通过使用volatile ConcurrentHashMap,向其中写入一个元素是否会建立“发生在之前”关系,即上述关系是否仍然有效
通常,您不需要将ConcurrentHashMap标记为易失性。ConcurrentHashMap代码本身内部存在跨越的内存障碍。如果ConcurrentHashMap字段经常被更改,那么我唯一会使用它,即它是非最终字段
你的代码看起来真的像是过早的优化。探查器是否向您表明这是一个性能问题?我建议你在地图上同步,我就完成了。有两个ConcurrentHashMap来解决这个问题对我来说似乎太过分了。谢谢你的回答!我刚刚写了一篇很长的评论,解释了为什么我认为我需要从双重检查锁定开始,只是为了意识到我不允许在这里发布太长的内容。我过几个小时再给你打电话。目前,让我把这个问题抛回到讨论中:有没有办法让ConcurrentHashMap在执行put时使用volatile行为;ConcurrentHashMap是完全并发的。如果您通过调用setupsDone.put来询问是否存在“先发生后发生”关系。。。答案是肯定的,但你不知道什么时候跨越了记忆的障碍。当put完成时,您确实知道有一个变量被跨越了。这种情况会发生在关系/内存障碍只影响ConcurrentHashMap之前,还是会像通常一样影响写入线程在发生之前可见的所有变量之前?也就是说,写入setupsDone会影响o在其他线程中的可见性吗?本周末我查看了ConcurrentHashMap的源代码,并试图理解它。Java 6中的ConcurrentHashMap有一个瞬时的volatile字段计数,它显然由get方法读取,并且仅当使用put添加了一个新键时才写入,因此我认为建立了一个before-before关系。我没有检查覆盖。在Java7中,count不再声明为volatile,而是瞬态volatile HashEntry[]表;我没有详细检查它的用法。谢谢你的回答!我刚刚写了一篇很长的评论,解释了为什么我认为我需要从双重检查锁定开始,只是为了意识到我不允许在这里发布太长的内容。我过几个小时再给你打电话。目前,让我把这个问题抛回到讨论中:有没有办法让ConcurrentHashMap在执行put时使用volatile行为;ConcurrentHashMap是完全并发的。如果您通过调用setupsDone.put来询问是否存在“先发生后发生”关系。。。答案是肯定的,但你不知道什么时候跨越了记忆的障碍。当put完成时,您确实知道有一个变量被跨越了。这种情况会发生在关系/内存障碍只影响ConcurrentHashMap之前,还是会像通常一样影响写入线程在发生之前可见的所有变量之前?也就是说,写入setupsDone会影响o在其他线程中的可见性吗?本周末我查看了ConcurrentHashMap的源代码,并试图理解它。Java 6中的ConcurrentHashMap有一个瞬时的volatile字段计数,它显然由get方法读取,并且仅当使用put添加了一个新键时才写入,因此我认为建立了一个before-before关系。我没有检查覆盖。在Java7中,counter不再声明为volatile,而是transie
nt volatile HashEntry[]表;我没有详细研究它的用法。嗨,加博什,谢谢你的建议;看起来真的很棒。不过,只有一个问题:当你说volatile的isSetupDone不是这样的时候,你的意思是我们应该让isSetupDone volatile像这样:volatile布尔值isSetupDone=false@没错。对不起,这是我的错误,我要更新那一行。我的意思是我被设置成易变的。嗨,加博什,谢谢你的建议;看起来真的很棒。不过,只有一个问题:当你说volatile的isSetupDone不是这样的时候,你的意思是我们应该让isSetupDone volatile像这样:volatile布尔值isSetupDone=false@没错。对不起,这是我的错误,我要更新那一行。我的意思是说我被设置成易变的。