在ConcurrentHashMap中从/中同时获取/创建项目的两种方法,哪一个更好?

时间:2013-05-04 12:37:14

标签: java concurrency

我发现自己使用两种略有不同的方法从ConcurrentHashMap中获取/创建项目,我想知道哪一项更好。

第一种方式:

public class Item {
  private boolean m_constructed;
  ...
  public void construct() {
    if (m_constructed) {
      return;
    }

    synchronized (this) {
      if (m_constructed) {
        return;
      }

      // Some heavy construction

      m_constructed = true;
    }
  }
}

ConcurrentHashMap<String, Item> m_items = new ConcurrentHashMap<String, Item>();

...
// The following code is executed concurrently by multiple threads:
public Item getOrCreateItem(String key) {
  Item newItem = new Item();                          // The constructor is empty
  Item item = m_items.putIfAbsent(key, newItem);
  if (item == null) {
    item = newItem;
  }
  item.construct();                                  // This is the real construction
  return item;
}

请不要评论synchronize (this)中使用的评论。我知道使用this作为锁定对象的困惑,但在这个特定的例子中我很好。

第二种方式:

public class Item {
  private boolean m_constructed;
  ...
  public void construct() {
    // Some heavy construction

    m_constructed = true;
  }

  public void waitForConstruction() throws InterruptedException {
    while (!m_constructed) {
      Thread.sleep(50);
    }
  }
}

ConcurrentHashMap<String, Item> m_items = new ConcurrentHashMap<String, Item>();

...
// The following code is executed concurrently by multiple threads:
public Item getOrCreateItem(String key) {
  Item newItem = new Item();                          // The constructor is empty
  Item item = m_items.putIfAbsent(key, newItem);
  if (item == null) {
    item.construct();                                 // This is the real construction
    item = newItem;
  }
  item.waitForConstruction();
  return item;
}

我想知道一种方式是否优于另一种方式。有什么想法吗?

修改

关于背景的几句话。 Item映射由多个线程同时填充,所有线程都执行getOrCreateItem。没有代码尝试以任何其他方式访问地图。一旦人口结束,地图永远不会被修改并变为对只读访问开放。因此,没有人可以在Item方法之外获得部分构造的getOrCreateItem实例。

EDIT2

感谢所有答案。我采用了第一种方法,建议修复:

public class Item {
  private volatile boolean m_constructed;         // !!! using volatile
  ...
  public void construct() {
    if (m_constructed) {
      return;
    }

    synchronized (this) {
      if (m_constructed) {
        return;
      }

      // Some heavy construction

      m_constructed = true;
    }
  }
}

ConcurrentHashMap<String, Item> m_items = new ConcurrentHashMap<String, Item>();

...
// The following code is executed concurrently by multiple threads:
public Item getOrCreateItem(String key) {
  Item item = m_items.get(key);                         // !!! Checking the mainstream case first
  if (item == null) {
    Item newItem = new Item();                          // The constructor is empty
    item = m_items.putIfAbsent(key, newItem);
    if (item == null) {
      item = newItem;
    }
  }
  item.construct();                                  // This is the real construction
  return item;
}

当然,我假设没有代码使用getOrCreateItem方法之外的任何其他方式访问项目地图。在我的代码中也是如此。

2 个答案:

答案 0 :(得分:3)

首先,请注意您的解决方案的已正确同步。必须使m_constructed标志变为易失性才能使其工作。否则,您可能会遇到thread visibility problems,因为您对此成员的读取权限不受锁定保护。

无论如何,item类与Future的概念太相似了。如果将Future实施存储为地图值(例如FutureTask),则问题就解决了。

答案 1 :(得分:1)

我认为第一个解决方案并不是那么糟糕。 m_constructed变量必须是volatile才能正常工作,正如John Vint建议的那样,建议在执行putIfAbsent()之前调用ConcurrentMap.get()。

我怀疑最重要的调用路径是稳定状态访问(线程访问已经添加到地图中并且已经构建的项目)。在这种情况下,使用第一个解决方案,您将执行ConcurrentHashMap.get()调用和volatile读取(在m_constructed上),这不是很糟糕。

第二种解决方案很差,因为它涉及不必要的繁忙旋转循环。当根据John Vint的建议将其转换为使用CountDownLatch时,稳态性能将与上述类似:ConcurrentHashMap.get()调用和CountDownLatch.await()应该类似于无竞争的volatile读取。但是,唯一的缺点是它为Item增加了更多的内存。