代码之家  ›  专栏  ›  技术社区  ›  Eugene Mamaev

双重检查锁定(DCL)及其修复方法

  •  1
  • Eugene Mamaev  · 技术社区  · 7 年前

    我在读一本 article 关于双重检查锁定。讨论的问题如下:

    public class MyFactory {
        private static MyFactory instance;
    
        public static synchronized MyFactory getInstance() {
            if (instance == null)
                instance = new MyFactory();
            return instance;
        }
    
        private MyFactory() {}
    }
    

    建议的方法之一是如何避免在每个 getInstance() 调用是使用类加载器:

    public class MyFactory {
        private static final MyFactory instance;
    
        static {
            try {
                instance = new MyFactory();
            } catch (IOException e) {
                throw new RuntimeException("Darn, an error's occurred!", e);
            }
        }
    
        public static MyFactory getInstance() {
            return instance;
        }
    
        private MyFactory() throws IOException {
            // read configuration files...
        }
    }
    

    作者说:“……JVM确保在第一次引用和加载类时,只调用一次静态初始化代码。”我完全同意。但我不理解以下内容:“使用类加载器通常是我处理惰性静态初始化的首选方式。”

    第二个代码段是惰性静态初始化吗?

    3 回复  |  直到 7 年前
        1
  •  2
  •   dSH    7 年前

    这不是懒惰。这是懒惰的(使用类加载器):

    public class Singleton {
    
        public static class SingletonHolder {
            public static final Singleton HOLDER_INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.HOLDER_INSTANCE;
        }
    }
    
        2
  •  1
  •   xiaofeng.li    7 年前

    不完全是这样。初始化发生在 MyFactory 类已加载,而不是在 getInstance() 已调用。如果要在调用方法时初始化实例,可以使用holder类。

    public class MyFactory {
        private static final class Holder {
            private static final MyFactory instance = new MyFactory();
        }
    
        public static MyFactory getInstance() {
            return Holder.instance;
        }
    }
    
        3
  •  1
  •   Shyam Baitmangalkar    7 年前

    玩双重检查锁定就像玩火。有很多方法可以把它弄错。

    双重检查锁定习惯用法的目的是避免不必要的 synchronization . 因此,您的第一个代码段不属于该类别。

    接下来是第二个代码段,如果要创建的单例是 static ,然后有一个紧凑的解决方案,您可以将单例定义为 静止的 a中的字段 独立类 而不是在同一个班级。

    class MyFactorySingleton {
      static MyFactory singleton = new MyFactory();
    } 
    

    Java的这种语义保证了字段在被引用之前不会被初始化,并且任何访问该字段的线程都会看到初始化该字段所产生的所有写操作。

    volatile 语义学是这样说的

    不稳定的 永远不会 本地缓存线程。所有读写操作将直接转到“main” 对变量的访问就像它被封装在 同步块,自身同步。

    class MyFactory {
      private volatile MyFactory instance = null;
    
      public MyFactory getInstance() {
         if(instance == null) {
            synchronized(MyFactory.class) {
               if(instance == null) {
                  instance = new MyFactory();
               }
            }
         }
         return instance;
      }
    }
    

    Joshua Bloch教授和他的合著者解释了在这种情况下双重检查锁定是如何出错的 article . 值得一读。