happens-before原則


一、問題

一下代碼經測試,打開注釋行,子線程就不會陷入while死循環了,為什么呢

public class VolatileTest3   {
    // b使用volatile修飾
    public static volatile long b = 0;
    //消除緩存行的影響
    public static long a1,a2,a3,a4,a5,a6,a7,a8;
    // c不使用volatile修飾
    public static long c = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (c == 0) {
                //long x = b;
            }
            System.out.println("c=" + c);
        }).start();

        Thread.sleep(100);

        b = 1;
        c = 1;
    }
}

 

         可以理解為:如果不加volatile,java編程語言的java memory model允許一個線程讀到另一個線程任何一次寫進去的值(可以是初值0也可以是主線程寫入的1),只要不是happens-after它的就可以。但這個程序兩個線程沒有任何同步,所以沒有任何happens-before關系。所以,就算主線程寫,另一個線程永遠讀到c == 0,也是允許的。只要允許,你看到的“程序永遠退不出去”就是合理的結果。至於為什么會出現這種現象,你暫且認為是巧合吧,反正這是《Java語言標准》允許的,JVM沒做錯什么。
         但是一旦加上volatile,所有線程對c的讀寫操作就構成一個序列。因為main早晚會執行完,所以早晚會又一個對c的寫操作,寫入1。由於new thread會不斷讀c,早晚會有一次讀happens after那個往c里寫1的操作。對於volatile變量來說,寫之后的讀都能看到那個寫的值“1”。所以那個new thread早晚可以看到c == 1。

         或者可以理解為:共享變量c被兩個線程讀寫,cpu緩存行存在兩份值,如果不加volatile則更新過后的c的值不知道什么時候會更新到主存,加了volatile后會立即同步到主存,緩存行無效,另一個線程會重新從主存加載新值到cpu緩存行。

二、關於happens-before原則

    

        Java內存模型中的happens-before是什么?為什么會有這東西的存在?

        其實我們學習的初期或者時間很急迫的時候我們都是死記硬背,沒有理解這東西背后的含義和為什么需要這東西。沒辦法比如項目趕,一個新東西肯定是上手先,但是等我們空下來回過頭來,我們還是需要去理解這些知識,只有這樣我才能深刻的記住,並且運用熟練。

         happens-before字面翻譯過來就是先行發生,A happens-before B 就是A先行發生於B?

不准確!在Java內存模型中,happens-before 應該翻譯成:前一個操作的結果可以被后續的操作獲取。講白點就是前面一個操作把變量a賦值為1,那后面一個操作肯定能知道a已經變成了1。

我們再來看看為什么需要這幾條規則?

因為我們現在電腦都是多CPU,並且都有緩存,導致多線程直接的可見性問題。詳情可以看我之前的文章面試官:你知道並發Bug的源頭是什么嗎?

所以為了解決多線程的可見性問題,就搞出了happens-before原則,讓線程之間遵守這些原則。編譯器還會優化我們的語句,所以等於是給了編譯器優化的約束。不能讓它優化的不知道東南西北了!

   

咱們來看看這幾條規則

        程序次序規則:在一個線程內一段代碼的執行結果是有序的。就是還會指令重排,但是隨便它怎么排,結果是按照我們代碼的順序生成的不會變!

管程鎖定規則:就是無論是在單線程環境還是多線程環境,對於同一個鎖來說,一個線程對這個鎖解鎖之后,另一個線程獲取了這個鎖都能看到前一個線程的操作結果!(管程是一種通用的同步原語,synchronized就是管程的實現)

         volatile變量規則:就是如果一個線程先去寫一個volatile變量,然后一個線程去讀這個變量,那么這個寫操作的結果一定對讀的這個線程可見。

  線程啟動規則:在主線程A執行過程中,啟動子線程B,那么線程A在啟動子線程B之前對共享變量的修改結果對線程B可見。

  線程終止規則:在主線程A執行過程中,子線程B終止,那么線程B在終止之前對共享變量的修改結果在線程A中可見。

  線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程代碼檢測到中斷事件的發生,可以通過Thread.interrupted()檢測到是否發生中斷。

  傳遞規則:這個簡單的,就是happens-before原則具有傳遞性,即A happens-before B , B happens-before C,那么A happens-before C。

  對象終結規則:這個也簡單的,就是一個對象的初始化的完成,也就是構造函數執行的結束一定 happens-before它的finalize()方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM