指令重排序、內存屏障很難?看完這篇你就懂了!

據說微信搜索《Java魚仔》會變動強哦!java

本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git

面試官在問到多線程編程的時候,指令重排序、內存屏障常常會被提起。若是你對這二者有必定的理解,那這就是你的加分項。github

(一)什麼是指令重排序

爲了使處理器內部的運算單元能儘可能被充分利用,處理器可能會對輸入的代碼進行亂序執行優化,處理器會在計算以後將亂序執行的結果重組,並確保這一結果和順序執行結果是一致的,可是這個過程並不保證各個語句計算的前後順序和輸入代碼中的順序一致。這就是指令重排序。面試

簡單來講,就是指你在程序中寫的代碼,在執行時並不必定按照寫的順序。編程

在Java中,JVM可以根據處理器特性(CPU多級緩存系統、多核處理器等)適當對機器指令進行重排序,最大限度發揮機器性能。緩存

Java中的指令重排序有兩次,第一次發生在將字節碼編譯成機器碼的階段,第二次發生在CPU執行的時候,也會適當對指令進行重排。微信

(二)復現指令重排序

光靠說不容易看出現象,下面來看一段代碼,這段代碼網上出現好屢次了,但確實很能復現出指令重排序。我把解釋放在代碼後面。多線程

public class VolatileReOrderSample {
    //定義四個靜態變量
    private static int x=0,y=0;
    private static int a=0,b=0;

    public static void main(String[] args) throws InterruptedException {
        int i=0;
        while (true){
            i++;
            x=0;y=0;a=0;b=0;
            //開兩個線程,第一個線程執行a=1;x=b;第二個線程執行b=1;y=a
            Thread thread1=new Thread(new Runnable() {
                @Override
                public void run() {
                    //線程1會比線程2先執行,所以用nanoTime讓線程1等待線程2 0.01毫秒
                    shortWait(10000);
                    a=1;
                    x=b;
                }
            });
            Thread thread2=new Thread(new Runnable() {
                @Override
                public void run() {
                    b=1;
                    y=a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            //等兩個線程都執行完畢後拼接結果
            String result="第"+i+"次執行x="+x+"y="+y;
            //若是x=0且y=0,則跳出循環
            if (x==0&&y==0){
                System.out.println(result);
                break;
            }else{
                System.out.println(result);
            }
        }
    }
    //等待interval納秒
    private static void shortWait(long interval) {
        long start=System.nanoTime();
        long end;
        do {
            end=System.nanoTime();
        }while (start+interval>=end);
    }
}

這段代碼雖然看着長,其實很簡單,定義四個靜態變量x,y,a,b,每次循環時讓他們都等於0,接着用兩個線程,第一個線程執行a=1;x=b;第二個線程執行b=1;y=a。ide

這段程序有幾個結果呢?從邏輯上來說,應該有3個結果:性能

當第一個線程執行到a=1的時候,第二個線程執行到了b=1,最後x=1,y=1

當第一個線程執行完,第二個線程纔剛開始,最後x=0,y=1

當第二個線程執行完,第一個線程纔開始,最後x=1,y=0

理論上不管怎麼樣都不可能x=0,y=0;

可是當程序執行到幾萬次以後,居然出現了00的結果:

在這裏插入圖片描述

這就是由於指令被重排序了,x=b先於a=1執行,y=a先於b=1執行。

在這裏插入圖片描述

(三)經過什麼方式禁止指令重排序?

Volatile經過內存屏障能夠禁止指令重排序,內存屏障是一個CPU的指令,它能夠保證特定操做的執行順序。

內存屏障分爲四種:

StoreStore屏障、StoreLoad屏障、LoadLoad屏障、LoadStore屏障。

JMM針對編譯器制定了Volatile重排序的規則:

在這裏插入圖片描述

光看這些理論可能不容易懂,下面我就用通俗的話語來解釋一下:

首先是對四種內存屏障的理解,Store至關因而寫屏障,Load至關因而讀屏障。

好比有兩行代碼,a=1;x=2;而且我把x修飾爲volatile。

執行a=1時,它至關於執行了一次普通的寫操做;

執行x=2時,它至關於執行了一次volatile的寫操做;

所以在這兩行命令之間,就會插入一個StoreStore屏障(前面是寫後面也是寫),這就是內存屏障。

再讓咱們看錶,若是第一個操做是普通寫,第二個操做是volatile寫,那麼表格中對應的值就是NO,禁止重排序。這就是Volatile進行指令重排序的原理。

如今,咱們只須要把上面代碼的x和y用volatile修飾,就不會發生指令重排序了(若是你能經過表推一遍邏輯,你就能懂了)。

相關文章
相關標籤/搜索