用两个例子来讲明:java
public class Singleton { private static Singleton uniqueSingleton; private Singleton() {} public static Singleton getInstance() { if (null == uniqueSingleton) {//先判断是否为空,没必要每次都加锁 synchronized (Singleton.class) {//再获取锁 if (null == uniqueSingleton) {//再次判断是否为空,由于第二个线程获取到锁后,继续执行,可是第一个线程已经初始化完成了 uniqueSingleton = new Singleton();//初始化对象 } } } return uniqueSingleton; } }
上述代码是一个双重锁的单例模式实现方式,但存在隐患
咱们须要了解初始化对象的过程,包含的指令大体以下:
1.分配内存空间
2.初始化对象
3.将对象指向分配的内存空间
有些编译器为了提高效率,会将2 3的顺序倒置(重排序,先将对象指向分配的内存空间再初始化),所以可能发生读取到未初始化完成的对象
如何解决?
使用volatile
关键字,禁止重排序编程
private volatile static Singleton uniqueSingleton;
public class NoVisibility{ private static boolean ready; private static int number; private static class ReaderThrad extends Thread{ public void run(){ while(!ready){ Thread.yield(); } System.out.println(number); } } public static void main(String [] args){ new ReaderThread().start(); number = 42; ready = true; } }
上述代码源自<<Java并发编程实践>>
书中
可能发生的现象:多线程
Thread.yield()
的意思是将当前状态转换为就绪状态,ReaderThread立刻又获取到了执行机会,可能致使main线程永远获取不到执行的机会number=42
ready=true
不知足Happens-Before
原则,所以JVM能够对这两个操做任意的重排序,便可能先执行ready=true
再执行number=42
结语:本篇幅较短,不能深刻的将多线程中有关重排序的问题的讲得透彻,若从发散思惟的角度来看来此问题,涉及到的相关知识点很是多,想深刻了解建议阅读<<Java并发编程实践>>
<<深刻理解Java虚拟机>>
并发