分享

重排序

 印度阿三17 2018-09-27

目录

重排序

重排序是编译器和处理器优化程序性能的一种重要方式。

重排序有两种类型:

  • 编译期优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应的机器指令的执行顺序。

PS:请注意重排的是执行顺序。

单线程程序语义》能够保证重排序不影响单线程程序的执行结果,但是重排序可能改变多线程程序的执行结果。

【例一:编译期优化的重排序】

class RecorderExample{
    int a = 0;
    boolean flag = false;
    public void write(){
        a = 1;  // 1
        flag = true;    // 2
    }
    public void read(){
        if(!flag){
            a = 2;
        }
    }
}

理想情况下可能的时序:

线程A 线程B
a=1
if(!flag)
a=2
flag=true .

操作1 与操作2 重排序的情况下,可能的时序:

线程A 线程B
flag=true
if(!flag)
a=1 .

两种情况结果完全不同。

如何解决?两个方法都用 synchronized 关键字修饰加锁,此时同步块内的重排序并不影响多线程的执行结果。

【例二:指令级并行的重排序】

以懒汉式单例的双重检查锁定为例:

public class Singleton {
    private static Singleton instance = null;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

对象的初始化可以分解为以下伪代码:

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置 instance 指向内存空间

而编译器很可能为了优化程序的执行效率进行指令重排序,即对 2 和 3 进行互换。

memory = allocate();  // 1:分配对象的内存空间
instance = memory;   // 3:设置 instance 指向内存空间(此时对象还没有被初始化!)
ctorInstance(memory);   // 2:初始化对象

在重排序的情况下,多线程访问可能会得到尚未初始化的对象。

如何解决?可以使用 volatile 关键字修饰 instance 变量,提供内存屏障禁止指令重排序。

append

【Q】 单线程中会不会得到一个尚未完全初始化的对象呢?

虽然单线程中允许上述重排序,但当使用 instance 引用的时候其指向的对象已经初始化成功了。

来源:http://www./content-4-27131.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多