Java线程安全单例模式的几种写法

348 阅读1分钟

一、饿汉式

在 Main 这个类被加载的时候,singleton 静态实例就已经被创建并初始化好了,因此饿汉式单例一定是线程安全的。

public class Main {
    private static final Object singleton = new Object();

    public static Object getSingleton() {
        return singleton;
    }
}

2. 懒汉式(双重检测锁)

双重检测锁介绍的文章有很多,这里就不赘述了。比较值得关注的是,在早期 JDK 中需要对 singleton 声明为 volatile 以禁用对象创建动作的指令重排。

若不声明为 volatile,在指令重排后,new Object 这个操作会先分配内存地址,再将引用指向内存地址,最后才是在内存地址上创建实例。这就可能导致,线程 A 还未在内存地址上创建实例,线程 B 就调用 getSingleton() 获取到实例并使用了,这就会导致空指针异常。

这个问题具体是在哪个版本被解决我也不太清楚,总之 JDK1.8 往后都不会有这种问题。

public class Main {
    private static Object singleton;

    public static Object getSingleton() {
        if (singleton == null) {
            synchronized (Main.class) {
                if (singleton == null) {
                    singleton = new Object();
                }
            }
        }
        return singleton;
    }
}

3. 静态内部类(推荐)

静态内部类 SingletonHolder 并不会在 Main 被加载时就被创建,只有当调用了 getSingleton() 方法时 SingletonHolder 才会被加载,这是与饿汉式单例最大的区别。

public class Main {
    private static class SingletonHolder {
        private static final Object singleton = new Object();
    }

    private static Object getSingleton() {
        return SingletonHolder.singleton;
    }
}

4. 枚举

基于 Java 枚举的特性,保证了实例创建的线程安全以及唯一性。这种方式也是比较简单且推荐使用的。

public enum Main {

    INSTANCE;

    private Object singleton = new Object();

    public Object getSingleton() {
        return singleton;
    }
}