双重检查锁定模式

双重检查锁定模式(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;
    }
}

(注意上面的 synchronizedvolatile

单线程正确版

一般情况下,我们如果要实现单例,会这么写:


public class Singleton {
    private static UUID uuid;

    public static UUID getInstance() {
        if (uuid == null) { // ①
            uuid = UUID.randomUUID(); // ②
        }

        return uuid; // ③
    }
}

这个版本的问题是:如果多线程运行,则在 ① 处判断时会有多个线程为真,从而导致语句 ② 被执行多次。

synchronized 正确版

很简单的思路是在方法上加上 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 的初始化就使用了类似的技巧。

参考文献

什么是 double checked locking