双重检查单例模式,单例对象为何要加上volatile关键字?

代码:

class Singleton{
    private volatile static Singleton instance = null;
    
    private Singletion{}

    public static Singleton getInstance(){
        if(instance==null){                        //#1
            synchronized(Singleton.class){         //#2
                if(instance==null){                //#3
                    instance = new Singletion();   //#4
                }else{
                    System.out.println("test");    //#5
                }
            }
        }
        return instance;
    }

}

单例对象为何要加上volatile关键字?有如下两方面原因:

(1)保证可见性:

如果"private static Singleton instance = null;":

1. thread1进入#1, 这时thread1的instance为null,thread1让出CPU资源给thread2.
2. thread2进入#1, 这时thread2的instance为null,thread2让出CPU资源给thread1.
3. thread1会依次执行#2,#3,#4,最终在thread1里面实例化了instance。thread1执行完毕让出CPU资源给thread2.
4. thread2接着#1跑下去,跑到#3的时候,由于#1里面拿到的instance还是null(并没有及时从thread1里面拿到最新的),所以thread2仍然会执行#3,#4.
5. 最后thread1和thread2都实例化了instance.

如果"private volatile static Singleton instance = null;":

1. thread1进入#1, 这时thread1的instance为null(java内存模型会从主内存里拷贝一份instance(null)到thread1),thread1让出CPU资源给thread2.
2. thread2进入#1, 这时thread2的instance为null(java内存模型会从主内存里拷贝一份instance(null)到thread2), thread2让出CPU资源给thread1.
3. thread1会依次执行#2,#3,#4, 最终在thread1里面实例化了instance(由于是volatile修饰的变量,会马上同步到主内存的变量去)。thread1执行完毕让出CPU资源给thread2.
4. thread2接着#1跑下去,跑到#3的时候,会又一次从主内存拷贝一份instance(!=null)回来,所以thread2就直接跑到了#5.
5. 最后thread2不会再重复实例化instance了.

(2)防止重排序:

在单例模式中,Instance instance= new Instance();   这一句代码不是原子操作,它可以分成三步原子指令:

1.分配内存地址;

2.new一个Instance对象;

3.将内存地址赋值给instance;

CPU为了提高执行效率,这三步操作的顺序可以是123,也可以是132。如果是132顺序的话,当把内存地址赋给inst后,inst指向的内存地址上面还没有new出来单例对象,这时候如果就拿到instance的话,它其实就是空的,会报空指针异常。

猜你喜欢

转载自blog.csdn.net/u012906122/article/details/103414518