双重检查锁定模式(Double checked locking)是软件设计的小技巧,第一重检查跳过大多数不需要竞争的情况,从而减少并发系统中的竞争开销。它经常被用在在“惰性初始化” (lazy initialization) 中,例如实现一个线程安全的单例。如下示例:
public class Singleton {
    private static volatile UUID uuid;
    public static UUID getInstance() {
        if (uuid == null) {
            synchronized (Singleton.class) {
                if (uuid == null) {
                    uuid = UUID.randomUUID();
                }
            }
        }
        return uuid;
    }
}
		
(注意上面的		synchronized
和		volatile
)	
一般情况下,我们如果要实现单例,会这么写:
public class Singleton {
    private static UUID uuid;
    public static UUID getInstance() {
        if (uuid == null) { // ①
            uuid = UUID.randomUUID(); // ②
        }
        return uuid; // ③
    }
}
		这个版本的问题是:如果多线程运行,则在 ① 处判断时会有多个线程为真,从而导致语句 ② 被执行多次。
很简单的思路是在方法上加上 synchronized 来强制同步:
public class Singleton {
    private static UUID uuid;
    public synchronized static UUID getInstance() {
        if (uuid == null) {
            uuid = UUID.randomUUID();
        }
        return uuid;
    }
}
		这个版本是正确的,只是在高并发的情况下,尽管已经初始化完毕,也要竞争锁,效率低。
鉴于版本二性能不好,我们争取将锁放在		uuid == null
的 if 语句之内:	
public class Singleton {
    private static UUID uuid;
    public static UUID getInstance() {
        if (uuid == null)
            synchronized (Singleton.class) {
                if (uuid == null) {
                    uuid = UUID.randomUUID();
                }
            }
        }
        return uuid;
    }
}
		这个版本看似无可挑剔,而且绝大多数情况下测试会通过,但它是错误的。
这其中的理由很复杂,并且需要很强的底层知识才能完全理解(如 java 内存模型,指令重排等等)。我们只需要记住,Java 1.5 之后,为对象加上		volatile
关键词即可。	
如果只是需要初始化单例,可以使用下面这种形式:
public class Singleton {
    private static class Holder {
        private static UUID uuid = UUID.randomUUID();
    }
    public static UUID getInstance() {
        return Holder.uuid;
    }
}
		
内部静态类		Holder
只有在初次被使用时才会被加载,而只有		getInstance
方法才会使用它。这种方法的正确性是由 Java 类加载器保证的,在加载类的时候只会是单线程的。	
只不过这种方法比较局限,只适合初始化单例。而 double-checked locking 使用范围更广,事实上它在 Java 源码里还有很多使用,如 ConcurrentHashMap 的初始化就使用了类似的技巧。