通过双重检查锁定

通过双重检查锁定

本文介绍了通过双重检查锁定,是否可以在不保证之前将数据放入易失的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?

推荐答案

是的,这是正确的. 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-01 17:01