简体   繁体   English

使用双重检查锁定,在保证之前是否发生了对易失性ConcurrentHashMap的放置?

[英]With double-checked locking, does a put to a volatile ConcurrentHashMap have happens-before guarantee?

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

Now, because we have groups of threads that all share this class, we changed the boolean to a ConcurrentHashMap as follows: 现在,因为我们有共享这个类的线程组,所以我们将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

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. 我现在的问题是:如果我将一个标准的Object声明为volatile ,我只会在读取或写入它的引用时建立一个先发生过的关系。 Therefore writing an element within that Object (if it is eg a standard HashMap , performing a put() operation on it) will not establish such a relationship. 因此,在该Object中编写一个元素(如果它是例如标准HashMap ,对它执行put()操作)将不会建立这样的关系。 Is that correct? 那是对的吗? (What about reading an element; wouldn't that require to read the reference as well and thus establish the relationship?) (阅读元素怎么样;这也不需要读取引用,从而建立关系?)

Now, with using a volatile ConcurrentHashMap, will writing an element to it establish the happens-before relationship, ie will the above still work ? 现在,使用一个易变的ConcurrentHashMap, 向它写一个元素建立之前发生的关系,即上述仍然有用吗?

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. 更新:这个问题的原因以及为什么双重检查锁定很重要:我们实际设置的(而不是Object)是MultiThreadedHttpConnectionManager ,我们将一些设置传递给我们,然后我们将其传递给我们设置的HttpClient也是,我们回来了。 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. 我们最多有10组,每组最多100个线程,我们使用双重检查锁定,因为我们不想在需要获取其组的HttpClient时阻止它们,因为整个设置将用于帮助性能测试。 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", ie establish a happens-before relationship, as the volatile boolean did, when performing a put() on the ConcurrentHashMap ? ;) (我意识到问题的原因有点具体,但我希望问题本身足够有趣: 有没有办法让ConcurrentHashMap使用“volatile行为”,即建立一个发生在之前的关系,作为volatile booleanConcurrentHashMap上执行put()时做了什么? ;)

Yes, it is correct. 是的,这是正确的。 volatile protects only that object reference, but nothing else. volatile仅保护该对象引用,但不保护其他内容。

No, putting an element to a volatile HashMap will not create a happens-before relationship, not even with a ConcurrentHashMap . 不,将元素放到volatile HashMap中不会创建发生在之前的关系,即使使用ConcurrentHashMap不会。

Actually ConcurrentHashMap does not hold lock for read operations (eg containsKey() ). 实际上ConcurrentHashMap没有锁定读取操作(例如containsKey() )。 See ConcurrentHashMap Javadoc. 请参见ConcurrentHashMap Javadoc。

Update : 更新

Reflecting your updated question: you have to synchronize on the object you put into the CHM. 反映您更新的问题:您必须同步放入CHM的对象。 I recommend to use a container object instead of directly storing the Object in the map: 我建议使用容器对象,而不是直接在地图中存储Object

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;
}

Note, that at creation , at most one thread may create ObjectContainer . 注意,在创建时 ,最多一个线程可以创建ObjectContainer But at initialization each groups may be initialized in parallel (but at most 1 thread per group). 但是在初始化时,每个组可以并行初始化(但每组最多1个线程)。

It may also happen that Thread T1 will create the ObjectContainer, but Thread T2 will initialize it. 也可能发生Thread T1将创建ObjectContainer,但是Thread T2将初始化它。

Yes, it is worth to keep the ConcurrentHashMap , because the map reads and writes will happen at the same time. 是的,保留ConcurrentHashMap是值得的,因为地图读写将同时发生。 But volatile is not required, since the map object itself will not change. 但是不需要volatile ,因为map对象本身不会改变。

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). 令人遗憾的是,仔细检查并不总是有效,因为编译器可能会创建一个字节码,重新使用containers.get(groupId)的结果(而不是volatile isSetupDone的情况)。 That's why I had to use containsKey for the double-checking. 这就是我必须使用containsKey进行双重检查的原因。

Therefore writing an element within that Object (if it is eg a standard HashMap, performing a put() operation on it) will not establish such a relationship. 因此,在该Object中编写一个元素(如果它是例如标准HashMap,对它执行put()操作)将不会建立这样的关系。 Is that correct? 那是对的吗?

Yes and no. 是的,不是。 There is always a happens-before relationship when you read or write a volatile field. 当您读取或写入volatile字段时,始终存在一种先发生过的关系。 The issue in your case is that even though there is a happens-before when you access the HashMap field, there is no memory synchronization or mutex locking when you are actually operating on the HashMap . 在您的情况下,问题是即使在您访问HashMap字段时发生了事件,但在实际操作HashMap时没有内存同步或互斥锁定。 So multiple threads can see different versions of the same HashMap and can create a corrupted data structure depending on race conditions. 因此,多个线程可以看到相同HashMap不同版本,并且可以根据竞争条件创建损坏的数据结构。

Now, with using a volatile ConcurrentHashMap, will writing an element to it establish the happens-before relationship, ie will the above still work? 现在,使用一个易变的ConcurrentHashMap,会向它写一个元素来建立之前发生的关系,即上述仍然有用吗?

Typically you do not need to mark a ConcurrentHashMap as being volatile . 通常,您不需要标记ConcurrentHashMapvolatile There are memory barriers that are crossed internal to the ConcurrentHashMap code itself. ConcurrentHashMap代码本身内部存在内存障碍。 The only time I'd use this is if the ConcurrentHashMap field is being changed often -- ie is non-final. 我唯一一次使用它是因为ConcurrentHashMap字段经常被更改 - 即非最终。

Your code really seems like premature optimization. 您的代码看起来似乎过早优化。 Has a profiler shown you that it is a performance problem? 有没有分析器向您显示这是性能问题? I would suggest that you just synchronize on the map and me done with it. 我建议你只是在地图上同步,我完成它。 Having two ConcurrentHashMap to solve this problem seems like overkill to me. 有两个ConcurrentHashMap来解决这个问题对我来说似乎有些过分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM