https://www.cnblogs.com/damonhuang/p/5431866.html
java单例模式与线程安全_java单例模式线程安全_Tifkingsly的博客-CSDN博客
代码:
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的话,它其实就是空的,会报空指针异常。