双重校验锁实现单例模式

package day1;
/** 特点:懒汉模式,只有使用时,才进行创建
 * 首次调用getInstance()才使用synchronized加锁,后续使用时无需加锁
 * 该代码存在效率问题,因为即使已经产生了单例之后,之后调用了getInstance()方法之后仍然还会加锁解锁,
 * 加锁和解锁会影响系统的性能
 * ====================>
 * 于是有了双重校验锁机制
 */

//问题一:final的作用:防止子类不适当地重写父类中的方法,破坏单例模式
public final class Singleton {
    private static Singleton instance = null;
    private Singleton() {} // 问题二:私有的构造方法,只能类内调用,但是私有不能防止反射来创建新的实例(反射机制很强大)
    public static Singleton getInstance() {
        synchronized (Singleton.class) {    // 避免多线程并发访问
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    // 如果Singleton实现了序列化接口,还要做什么来防止反序列化破坏单例
    // 加上以下方法
    // 有了这个方法,那么readResolve的结果会当成反序列化字节码生成的对象
    public Object readResolve() {
        return instance;
    }
}

final class Singleton_1 {
    private Singleton_1() {
    }

    private static Singleton_1 instance = null;

    public static Singleton_1 getInstance() {
        if (instance == null) {
            // 首次访问会同步,之后的不会进入synchronized方法
            synchronized (Singleton_1.class) {
                if (instance == null) {
                    instance = new Singleton_1();
                }
            }
        }
        return instance;
    }
}
/**
 * 但是该方法仍然存在问题
 *  *  if (instance == null) 在synchronized外部,instance静态变量不是有序的,synchronized无法禁止指令重排
 *  *  instance = new Singleton_1();
 *  *  字节码包含4部分:
 *  *  1. new 新建一个实例
 *  *  2. 复制这个实例的引用
 *  *  3. 通过该引用调用构造方法
 *  *  4. 引用用来进行赋值操作putstatic
 *  *
 *  *  JIT即时编译器进行指令重排,将4排到3的前面,那么t1还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作
 *  *  那么t2拿到的instance类变量是非空的,但是一个未初始化完毕的单例,那么就会出现问题
 *  *  解决办法,就是对instance使用volatile修饰
 */

// 懒汉式-完整版
final class Singleton_2 {
    private Singleton_2() {}
    private volatile static Singleton_2 instance = null;
    public static Singleton_2 getInstance() {
        // 读屏障,写屏障之后的不能指令重排
        if (instance == null) {
            // 首次访问会同步,之后的不会进入synchronized方法
            synchronized (Singleton_2.class) {
                if (instance == null) {
                    instance = new Singleton_2();
                    // 写屏障,写屏障之前的不能指令重排
                }
            }
        }
        return instance;
    }
}

// 饿汉式-完整版
final class Singleton_3 {
    // 1.私有的构造方法,只能类内调用
    private Singleton_3() {}

    // 2.静态变量的初始化,是在类的加载过程(类加载只有一次)中完成的,不会有多线程并发问题
    private static final Singleton_3 instance = new Singleton_3();

    // 3.创建一个对象,让外面的类可以拿到对象的引用
    // 这里使用方法返回,而不是直接把instance设置成public,的原因有很多:
    // 比如:使用方法的话,可以增加封装性;并且方法可以支持对泛型的控制,比较灵活
    public static Singleton_3 getInstance() {
        return instance;
    }
}

// 枚举类
// 枚举类构建的单例不能被反序列化和反射模式破坏
// instance在类加载阶段完成
// 枚举类的实例只有一个instance
enum Singelton {
    instance;
}

// 线程安全的实现形式 使用内部类
final class Singleton_4 {
    private Singleton_4() {}

    // 内部类 懒汉式,只有调用了getInstance方法,才创建单例
    private static class LazyHolder {
        // 内部类类加载的时候 对静态变量初始化 由JVM保证线程的安全性
        static final Singleton_4 instance = new Singleton_4();
    }

    public static Singleton_4 getInstance() {
        return LazyHolder.instance;
    }
}

 

posted @ 2021-06-15 21:53  Peterxiazhen  阅读(1288)  评论(0编辑  收藏  举报