java指令重排序
用两个例子来说明:
1.双重锁单例模式
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;
2.多线程下的状态判断
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并发编程实践>>
书中
可能发生的现象:
- 持续循环下去,ReaderThread可能永远看不到ready的值
Thread.yield()
的意思是将当前状态转换为就绪状态,ReaderThread马上又获取到了执行机会,可能导致main线程永远获取不到执行的机会 - 可能会打印0
ReaderThread可能看到了写入ready的值,但却没有看到写入number的值,因为发生了“重排序”现象,由于number=42
ready=true
不满足Happens-Before
原则,因此JVM可以对这两个操作任意的重排序,即可能先执行ready=true
再执行number=42
结语:本篇幅较短,不能深入的将多线程中有关重排序的问题的讲得透彻,若从发散思维的角度来看来此问题,涉及到的相关知识点非常多,想深入了解建议阅读<<Java并发编程实践>>
<<深入理解Java虚拟机>>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。