5.volatile:有序性

2018-06-13  本文已影响0人  xialedoucaicai

1.什么是有序性

程序按照写代码的先后顺序执行,就是有序的。程序难道还能不按代码顺序执行?这就涉及到CPU的指令重排序问题。

2.指令重排

处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。那它如何保证即使重排最终结果也能正确呢?
答案就是保证指令数据间依赖关系不会被重排所影响。看如下例子:

int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4

语句2 3 4之间存在相互依赖关系,所以重排的结果一定会保证2->3->4这个顺序,但是1 2顺序就不一定了。
这种重排在单线程中没有问题,但对于对线程就可能存在问题了,看如下代码:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
 //线程2:
while(!inited ){
   sleep()
}
doSomethingwithconfig(context);

线程1中语句1 2之间没有依赖,可能会先执行inited=true;这将导致线程2收到错误的消息,使用还未初始化的context。不过我代码是怎么都不能重现,它总是按代码顺序执行,这就没法验证了。
我们可以为inited加上volatile关键字,这样在对inited修改前,一定会保证之前的代码全部执行完了,就不会出现重排导致的问题了。
关于指令重排还有很多内容,比如happens-before原则,内存屏障等等,要完全理解可能要深入到硬件,比较复杂和专业,再深入下去感觉要走火入魔,怕钻进去出不来,所以浅尝辄止了,大家有兴趣可以继续深入了解。

3.最佳实践

volatile禁止指令重排,最典型的应用应该就是双重锁的单例模式了。看代码

public class SecondSingleton {
    //volatile关键字保证可见性 同时禁用指令重排(jdk1.5后生效)
    private static volatile SecondSingleton singleton;
    
    private SecondSingleton(){
        
    }
    
    /**
     * 双重检查锁实现单例模式
     * 推荐这样写,既保证线程安全,又延迟加载
     * @return
     */
    public static SecondSingleton getSingleton() {
        if (singleton == null) {
            synchronized (SecondSingleton.class) {
                if(singleton == null){
                    singleton = new SecondSingleton();
                }
            }
        }
        return singleton;
    }
}

为什么要使用volatile修饰singleton?
主要在于singleton = new SecondSingleton();这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:

  1. 给 singleton 分配内存
  2. 调用 SecondSingleton的构造函数来初始化成员变量
  3. 将singleton 对象指向分配的内存空间(执行完这步 singleton 就为非 null 了)

但是由于指令重排,上述第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1->2->3 也可能是 1->3->2。如果是后者,则在 3 执行完毕,2 未执行之前,另一个线程执行if (singleton == null) ,这时 singleton 已经是非 null 了,但却没有初始化,所以该线程会直接返回 singleton ,然后使用,自然就会报错了。

上一篇下一篇

猜你喜欢

热点阅读