关于volatile+synchronized保证Map中单例对象线程安全的问题

128 阅读1分钟

CGLIB源码中寻找动态代理会重写那些方法时,看到这样一段代码:

//定义的类变量
private static volatile Map<ClassLoader, ClassLoaderData> CACHE = new WeakHashMap<ClassLoader, ClassLoaderData>();

//--------------代码片段-------------------------
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
   synchronized (AbstractClassGenerator.class) {
      cache = CACHE;
      data = cache.get(loader);
      //volatile+synchronized的双重检测
      if (data == null) {
         //这部分不是很明白为什么要重新建立一个map后,再put进去,替换掉原来的map
         //为什么要这么写,发现很多这种利用map做缓存都是这样子 例如sentinel的NodeSelectorSlot 记录当前context和资源的DefaultNode
         Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
         data = new ClassLoaderData(loader);
         newCache.put(loader, data);
         CACHE = newCache;
      }
   }
}
  • volatile保证了CACHE变量的可见性
  • 双重检测+synchronized保证了CACHEput是线程安全的,对应的data也会只有一份(单例模式)

这是Sentinel框架中NodeSelectorSlot代码片段,也是用Map记录一些信息,那时并没有在意双重检测后为什么要新建Map后加入,然后替换掉原来的map。但是今天在找CGLIB生成代理类是支持那些方法时发现它在用map做单例缓存时(也就是上面的代码)也是这么做的!!!所以就很好奇为什么要这样写(单例模式的双重检测我能理解)这么写考虑因素是什么?直接put不行吗?涉及到知识的盲区了

//以context和字段为维度的统计节点 
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

//--------------------------------代码片段---------------------------------------
DefaultNode node = map.get(context.getName());
if (node == null) {
    synchronized (this) {
        node = map.get(context.getName());
        if (node == null) {
            node = new DefaultNode(resourceWrapper, null);
            //也是新建 在复制进去 
            HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
            cacheMap.putAll(map);
            cacheMap.put(context.getName(), node);
            map = cacheMap;
            // Build invocation tree
            ((DefaultNode) context.getLastNode()).addChild(node);
        }

    }
}

求助各位掘金大佬

  1. 为什么要重新创建map?