线程安全的单例模式

静态内部类加载

//静态内部类加载
class Singleton {
    private static Singleton singleton;

    private Singleton() {
    }

    private static class SingletonInner {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getSingleton() {
        return SingletonInner.instance;
    }
}

静态内部类不会在单例加载时加载,当调用 getSingleton() 方法时才会进行加载,达到类似懒汉式效果,并且也是线程安全的。

类的静态属性只会在第一次加载类时进行初始化,所以上面的方法JVM 帮助我们保证了线程的安全性,在类进行初始化时,其他线程无法进入。

JVM在类的初始化阶段(即在class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

枚举

//枚举方法
enum Singleton {
    INSTANCE;

    public void method() {
    }
}

//调用
class Text {
    public static void main(String[] args) {
        Singleton.INSTANCE.method();
    }
}
// ————————————————

public class User {
    //私有化构造函数
    private User(){ }
    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数
        private SingletonEnum(){
            user=new User();
        }
        public User getInstnce(){
            return user;
        }
    }
    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

自由串行化;保证只有一个实例;线程安全。(TODO 枚举类保证),而且可以避免反射破坏Effective Java 作者所提倡的方法,近乎完美,在继承场景下不适用。

懒汉式双重检查终极版

//懒汉式双重检查终极版(推荐)volatile
class Singleton {
    private static volatile Singleton singleton;//volatile可以禁止重排序,synchronized不行,因此去掉这句还是可能会因重排序导致线程不安全


    private Singleton() {
    }


    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();//这是关键
                }
            }
        }
        return singleton;
    }
}

此方法给singleton 的声明上加了关键字 volatile ,进而解决了低概率的线程不安全问题,如果去掉是不对的。volatile 起到禁止指令重排的作用,在它赋值完成之前,就不会调用读操作(singleton == null)。

//instance = new Instance();分解

memory=allocate();        //1:分配对象的内存空间
ctorInstance(memory);     //2:初始化对象
instance = memory;        //3:设置instance指向刚分配的内存地址

因为new操作可以被分解为如上三个操作,如果这三个操作重排序为132,其他线程就有可能拿到未经初始化的实例。所以去掉volatile会使得线程不安全。

posted @ 2019-05-06 10:47  wunsiang  阅读(132)  评论(0编辑  收藏  举报