我发现自己使用两种略有不同的方法从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
方法之外的任何其他方式访问项目地图。在我的代码中也是如此。
答案 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增加了更多的内存。