【剑指offer系列】-02实现Singleton模式(关键字:重排序、双重检测)

public class Main1 {
    public static void main(String[] args) {
        DoubleCheck instance = DoubleCheck.getInstance();
        DoubleCheck instance1 = DoubleCheck.getInstance();
        System.out.println(instance == instance1);
    }
}

/**
 * 1.单例饿汉模式
 私有化构造方法
 静态常量,提前创建好对象实例
 方法返回对象实例
 */
//提前创建的实例可能不会被使用到,导致浪费内存
class Singleton {
    private Singleton(){

    }
    public static final Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

/**
 * 2.懒汉模式
 * 私有构造方法
 * 什么时候需要实例什么时候创建
 * 且只有第一次时创建
 */
//并发时,可能判断均为Null,new了两个实例,不满足单例
class Lazy{
    private Lazy(){

    }
    public static Lazy instance;

    public static Lazy getInstance() {
        if (instance == null){
            instance = new Lazy();
        }
        return instance;
    }
}

/**
 * 3.双重检测机制
 */
public class Main2
{
    private static volatile Singleton instance;//4.防止重排序

    static class Singleton{
        private Singleton(){}

        public static Singleton getInstance(){
            if (instance == null){//1.为了并发;
                synchronized (new Singleton()){//2.为了单例
                    if (instance == null){//3.为了单例
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    public static void main(String[] args)
    {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}

/**
 * 1.当t1,t2,t3都来了,此时instance已存在,则可以立即根据此if快速判断,然后返回已存在的instance;
 *    如果没有这个if,其他不变,就会导致t1,t2,t3都需要获得锁,进入同步代码块中才能判断出是否instance已经被创建过了,这样会有大量积压
 *
 * 2.第一instace不存在,需要一个线程去创建,只能一个线程进入代码块去创建实例
 *
 * 3.此时t1,t2,t3都突破了第一层if判断,三者都认为instance实例还未被创建。比如t2获取了锁,如果没有第二层if判读的话,t2会直接创建一个实例,
 *    这样不管不顾的做法,完全没有判断在t2之前,是否t1已经获取过锁进入了代码块中已经创建好了instance实例
 *
 * 4.如果发生了重排序会导致拿到的单例,是未被初始化的单例
 *
 **

 //volatile:防止指令重排序
 /**
 * instance = new Singleton();分为三个动作
 * 1.堆内存开辟一片空间,比如0X01
 * 2.堆中创建Singleton,有初始化赋值的话,赋值
 * 3.将对象内存地址返回给对象引用变量instance,instance存有0X01,instance在栈帧的局部变量表中
 *
 * 因为cpu为了提高吞吐量,会自动的改变cpu流水线,即指令的操作先后顺序改变,对单线程没有影响,多线程有
 * eg: 执行顺序 2,3变成让你3,2,导致返回给instance引用对象地址,虽然不为Null,但是根本没有完成赋值
 * 导致t1执行同步代码执行结束的时候,t2判读,Instance!=null,但是instance本身没赋值;t2会直接返回instance
 */

猜你喜欢

转载自blog.csdn.net/tmax52HZ/article/details/107735473