双重检查锁定延迟初始化 - 我是否需要为Map设置volatile关键字

时间:2014-03-22 07:49:39

标签: java

Usage_in_Java & Effective Java 2开始,如果延迟初始化目标是单个变量,我理解我需要volatile个关键字。

但是,我正在执行多个延迟初始化,并将它们存储在ConcurrentHashMap中? Map是否也需要变化?

这是代码示例。

public class Utils {
    private static final Map<Integer, RemoteViews> remoteViewsMap = new ConcurrentHashMap<Integer, RemoteViews>();

    public static RemoteViews getRemoteViews(int resourceId) {
        RemoteViews remoteViews = remoteViewsMap.get(resourceId);
        if (remoteViews == null) {
            synchronized(remoteViewsMap){
                remoteViews = remoteViewsMap.get(resourceId);
                if (remoteViews == null) {
                    remoteViews = new RemoteViews("org.yccheok.gui", resourceId);
                    remoteViewsMap.put(resourceId, remoteViews);
                }
            }
        }
        return remoteViews;
    }
}

以上代码是否正确且线程安全?

1 个答案:

答案 0 :(得分:3)

不需要volatile关键字,因为ConcurrentHashMapConcurrentMap的实现提供了以下内存一致性效果:

  

在将对象作为键或值放入ConcurrentMap之前的线程中的操作发生在从另一个线程中的ConcurrentMap访问或删除该对象之后的操作之前

但是,这并不是您通常希望使用并发映射的方式。一般模式如下:

Object existing = concurrentMap.get(key);
// check if this key is already present
if (existing == null) {
  Object newObject = new Object();
  existing = concurrentMap.putIfAbsent(key, newObject); // atomic operation
  // another thread might have already stored mapping for key
  if (existing == null) {
    return newObject;
  }
}
return existing;

注意,它不能保护您免受同时调用new Object()的两个线程的攻击(如果新对象的创建很昂贵,这可能是一个问题),但它允许您完全避免显式同步。

更新:对于双重检查锁定,在您的情况下,它应如下所示:

Object value = concurrentMap.get(key);
if (value == null) {
  synchronized (lock) {
    value = concurrentMap.get(key);
    if (value == null) {
      value = new Object();
      concurrentMap.put(key, value);
    }
  }
}
return value;