多线程04--线程的有序性

上一篇:​​​​​​https://blog.csdn.net/fengxianaa/article/details/124425522

我们都知道java代码是逐句执行的,看以下代码:

public static void test(){
        int val = 1;			//code1
        boolean bool = true;	//code2
 }

上面代码,code1在code2之前,但是JVM在真正执行这段代码的时候并不保证code1一定会在code2前面执行,因为这里可能会发生指令重排序。

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排:

源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令

  • 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

虽然不能保证执行顺序跟代码顺序一致,但它保证单线程下程序最终执行结果和代码顺序执行的结果是一致的。以上代码,code1和code2哪个先执行并不影响整个方法的执行结果。

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程的执行结果,却会影响到多线程并发执行的结果。

在多线程情况下:

public class Demo3 {
    static int x = 0, y = 0;
    static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 10000; ++i){
            x = 0; y = 0; a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;
                }
            });
            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            one.start();other.start();
            one.join();other.join();
            if(x==0 && y==0){
                System.out.println("重排序了");
            }
        }
    }
}
//在多cpu环境下,以上代码即使输出了“重排序了”,也并不能体现重排序,因为工作内存的原因

可以通过synchronized和Lock来保证有序性,也可以通过volatile关键字来保证一定的“有序性”。

至于如何保证,先看下面代码

单例----DCL(Double Check Lock)

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

以上代码,在高并发情况先就有可能产生问题。问题在于:instance = new Singleton();这句代码。

这条语句实际上包含了三条指令:

memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址 

其中2、3的执行顺序是可以互换的。

用volatile修饰instance变量,就可以禁止2和3重排序。

为什么volatile可以禁止指令重排序?

通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障。

内存屏障:针对跨处理器的读写操作,它被插入到两个指令之间,作用是禁止编译器和处理器重排序

但并不是所有代码都能重排序:

public static void test(){
        int val = 1;			//code1
        boolean bool = true;	//code2
    	val +=1;				//code3
    	int age = val;			//code4
 }

以上代码,code3和code4的执行顺序就不能互换。因为重排序时是会考虑指令之间的数据依赖性。

猜你喜欢

转载自blog.csdn.net/fengxianaa/article/details/124425893