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并发编程实践>>书中
可能发生的现象:

  1. 持续循环下去,ReaderThread可能永远看不到ready的值
    Thread.yield()的意思是将当前状态转换为就绪状态,ReaderThread马上又获取到了执行机会,可能导致main线程永远获取不到执行的机会
  2. 可能会打印0
    ReaderThread可能看到了写入ready的值,但却没有看到写入number的值,因为发生了“重排序”现象,由于number=42 ready=true不满足Happens-Before原则,因此JVM可以对这两个操作任意的重排序,即可能先执行ready=true再执行number=42

结语:本篇幅较短,不能深入的将多线程中有关重排序的问题的讲得透彻,若从发散思维的角度来看来此问题,涉及到的相关知识点非常多,想深入了解建议阅读<<Java并发编程实践>> <<深入理解Java虚拟机>>


lianh
1 声望0 粉丝