通过双重检查锁定,是否可以在不保证之前将数据放入易失的ConcurrentHashMap? [英] With double-checked locking, does a put to a volatile ConcurrentHashMap have happens-before guarantee?

查看:122
本文介绍了通过双重检查锁定,是否可以在不保证之前将数据放入易失的ConcurrentHashMap?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

So far, I have used double-checked locking as follows:

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,如下所示:

Now, because we have groups of threads that all share this class, we changed the boolean to a ConcurrentHashMap as follows:

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,则在读取或写入其引用时,只会建立先于发生的关系.因此,在该对象中写入元素(如果它是例如标准HashMap,对其执行put()操作)将建立这种关系. 这是正确的吗? (有关读取元素的内容;那也不需要阅读引用并因此建立关系吗?)

My question now is: If I declare a standard Object as volatile, I will only get a happens-before relationship established when I read or write its reference. Therefore writing an element within that Object (if it is e.g. a standard HashMap, performing a put() operation on it) will not establish such a relationship. Is that correct? (What about reading an element; wouldn't that require to read the reference as well and thus establish the relationship?)

现在,使用易失的ConcurrentHashMap, 将向其中写入元素 建立事前发生的关系,即以上内容仍然有效 ?

Now, with using a volatile ConcurrentHashMap, will writing an element to it establish the happens-before relationship, i.e. will the above still work?

更新:此问题的原因以及为什么双重检查锁定很重要: 我们实际设置的(而不是对象)是MultiThreadedHttpConnectionManager,我们向其传递了一些设置,然后又传递给了HttpClient,我们也进行了设置,然后返回.我们最多有10组,每组最多100个线程,并且我们使用了双重检查锁定,因为我们不想在需要获取组的HttpClient时阻止它们中的每一个,因为整个设置将用于帮助进行性能测试.由于笨拙的设计和奇怪的平台,我们无法在外部运行对象,因此只能在外部运行,因此我们希望以某种方式使该设置起作用. (我意识到问题的原因有点具体,但我希望问题本身足够有趣: 是否有一种方法可以使ConcurrentHashMap使用易变行为",即确定发生情况? -在建立关系之前,就像在ConcurrentHashMap上执行put()volatile boolean所做的一样?? ;)

Update: The reason for this question and why double-checked locking is important: What we actually set up (instead of an Object) is a MultiThreadedHttpConnectionManager, to which we pass some settings, and which we then pass into an HttpClient, that we set up, too, and that we return. We have up to 10 groups of up to 100 threads each, and we use double-checked locking as we don't want to block each of them whenever they need to acquire their group's HttpClient, as the whole setup will be used to help with performance testing. Because of an awkward design and an odd platform we run this on we cannot just pass objects in from outside, so we hope to somehow make this setup work. (I realise the reason for the question is a bit specific, but I hope the question itself is interesting enough: Is there a way to get that ConcurrentHashMap to use "volatile behaviour", i.e. establish a happens-before relationship, as the volatile boolean did, when performing a put() on the ConcurrentHashMap? ;)

推荐答案

是的,这是正确的. volatile仅保护该对象引用,而没有其他保护.

Yes, it is correct. volatile protects only that object reference, but nothing else.

否,即使将元素放入volatile HashMap也不会创建事前发生的关系.

No, putting an element to a volatile HashMap will not create a happens-before relationship, not even with a ConcurrentHashMap.

实际上ConcurrentHashMap不会为读取操作保持锁定(例如containsKey()).请参见 ConcurrentHashMap Javadoc.

Actually ConcurrentHashMap does not hold lock for read operations (e.g. containsKey()). See ConcurrentHashMap Javadoc.

更新:

反映您更新的问题:您必须同步放入CHM的对象.我建议使用容器对象,而不是直接将Object存储在地图中:

Reflecting your updated question: you have to synchronize on the object you put into the CHM. I recommend to use a container object instead of directly storing the Object in the 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个线程).

Note, that at creation, at most one thread may create ObjectContainer. But at initialization each groups may be initialized in parallel (but at most 1 thread per group).

Thread T1可能会创建ObjectContainer,但Thread T2会初始化它.

It may also happen that Thread T1 will create the ObjectContainer, but Thread T2 will initialize it.

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

Yes, it is worth to keep the ConcurrentHashMap, because the map reads and writes will happen at the same time. But volatile is not required, since the map object itself will not change.

可悲的是,双重检查并不总是有效,因为编译器可能会在重复使用containers.get(groupId)结果的位置创建字节码(volatile isSetupDone并非如此).这就是为什么我必须使用containsKey进行双重检查的原因.

The sad thing is that the double-check does not always work, since the compiler may create a bytecode where it is reusing the result of containers.get(groupId) (that's not the case with the volatile isSetupDone). That's why I had to use containsKey for the double-checking.

这篇关于通过双重检查锁定,是否可以在不保证之前将数据放入易失的ConcurrentHashMap?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆