1

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

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

现在,因为我们有一组共享这个类的线程,所以我们将booleana更改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

我现在的问题是:如果我将标准声明Objectvolatile,我只会在读取或写入其引用时建立起之前的关系。因此,在该对象中编写一个元素(如果它是一个标准HashMap,则对其执行put()操作)将不会建立这种关系。那是对的吗?(阅读一个元素怎么样;那不需要阅读参考资料并因此建立关系吗?)

现在,使用 volatile ConcurrentHashMap,将向其写入元素建立起之前的关系,即上述方法仍然有效吗?

更新:这个问题的原因以及为什么双重检查锁定很重要: 我们实际设置的(而不是对象)是 a MultiThreadedHttpConnectionManager,我们将一些设置传递给它,然后我们将其传递给HttpClient我们设置的 a ,太,我们回来了。我们有多达 10 个组,每组最多 100 个线程,并且我们使用双重检查锁定,因为我们不想在每个需要获取组的时候阻塞它们HttpClient,因为整个设置将用于帮助提高性能测试。由于笨拙的设计和奇怪的平台,我们不能只从外部传入对象,所以我们希望以某种方式使这个设置工作。(我意识到这个问题的原因有点具体,但我希望这个问题本身足够有趣:有没有办法得到那个ConcurrentHashMap使用“易变行为”,即volatile booleanput()ConcurrentHashMap? ;)

4

2 回答 2

3

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

不,将元素放入volatileHashMap 不会创建发生前的关系,即使使用ConcurrentHashMap.

实际上ConcurrentHashMap并不为读操作持有锁(例如containsKey())。请参阅ConcurrentHashMap Javadoc。

更新

反映您更新的问题:您必须在放入 CHM 的对象上进行同步。我建议使用容器对象而不是直接将其存储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;
}

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

也可能Thread T1会创建 ObjectContainer,但Thread T2会对其进行初始化。

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

可悲的是,双重检查并不总是有效,因为编译器可能会创建一个字节码,它正在重用它的结果containers.get(groupId)(这不是 volatile isSetupDone 的情况)。这就是为什么我不得不使用containsKey双重检查。

于 2013-06-14T15:54:37.877 回答
1

因此,在该对象中写入一个元素(如果它是例如标准的 HashMap,对其执行 put() 操作)将不会建立这种关系。那是对的吗?

是和不是。volatile当你读或写一个字段时,总是有一个happens-before关系。您的情况的问题是,即使在您访问该HashMap字段时有发生之前,当您实际在HashMap. 因此,多个线程可以看到相同的不同版本,HashMap并且可以根据竞争条件创建损坏的数据结构。

现在,使用 volatile ConcurrentHashMap,向其写入元素会建立起之前的关系,即上述方法是否仍然有效?

通常,您不需要将 a 标记ConcurrentHashMap为 being volatileConcurrentHashMap代码本身内部存在内存障碍。我唯一会使用它是如果ConcurrentHashMap字段经常更改 - 即不是最终的。

您的代码确实看起来像是过早的优化。探查器是否向您表明这是一个性能问题?我建议你只在地图上同步,我就完成了。有两个ConcurrentHashMap来解决这个问题对我来说似乎有点矫枉过正。

于 2013-06-14T16:24:48.740 回答