多執行緒的安全性問題(三)

童話述說我的結局發表於2020-10-02

上次文章中有講到多執行緒帶來的原子性問題,並且就原子性問題講解了synchronized鎖的本質以及用法,今天我們就著前面的內容跟著講解,同樣,我們在講解前一樣通過一個DEMO來引出今天的主題-----可見性問題

 

public class Volatlle {
public static boolean stop=false;

public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
int i=0;
while (!stop){
i++;
// System.out.println("結果:"+i);
// try {
// Thread.sleep(0);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("結果:"+i);
});
thread.start();
Thread.sleep(1000);
stop=true;
}
}

  我們執行上面程式碼,理論上分析我們可能會覺得應該會輸出結果並且執行緒結束,但是我們看下圖會發現現實與相像的差距,我們想要的輸出一直沒有出來,而且執行緒一直無法結束,導致這種現像的發生就是我們今天要講的可見性問題。我們在外層加入一個值的變化,但子執行緒並不知道;為解決這種問題我們可以把public static boolean stop=false;加一個volatile關健字來解決;也可以在whie(!stop)中加入System.out.println("結果:"+i);輸出或者加個Thread.sleep(0)來解決可見性問題

 

 

 到了這裡相信很多小夥伴們有問題了,我們就問題一個個解決,第一個問題System.out.println;關於這個列印語句能解決可見性問題我要分兩問分解答:

  1. println底層用到了synchronized這個同步關鍵字,這個同步會防止迴圈期間對於stop值的快取。

         2.因為println有加鎖的操作,而釋放鎖的操作,會強制性的把工作記憶體中涉及到的寫操作同步到主記憶體,可以通過如下程式碼去證明。

        3.從IO角度來說,print本質上是一個IO的操作,我們知道磁碟IO的效率一定要比CPU的計算效率慢得多,所以IO可以使得CPU有時間去做記憶體重新整理的事情,從而導致這個現象。            比如我們可以在裡面定義一個new File()。同樣會達到效果。

第二個問題 :Thread.sleep(0)導致的可見性問題

     官方文件上是說,Thread.sleep沒有任何同步語義,編譯器不需要在呼叫Thread.sleep之前把快取在暫存器中的寫重新整理到給共享記憶體、也不需要在Thread.sleep之後重新載入快取在暫存器中的值。編譯器可以自由選擇讀取stop的值一次或者多次,這個是由編譯器自己來決定的。但是我個人的理解是Thread.sleep(0)導致執行緒切換,執行緒切換會導致快取失效從而讀取到了新的值。(文件位置:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3)

 

 一. volatile關鍵字(保證可見性)

     我們執行下面程式碼,然後在VM options:中通過彙編指令

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,`*Volatlle.*`看到彙編指令lock指令

public class Volatlle {
    public volatile static boolean stop=false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            int i=0;
            while (!stop){
                i++;

            }
            System.out.println("結果:"+i);
        });
        thread.start();
        Thread.sleep(1000);
        stop=true;
    }
}

  

 

 

我們通過彙編發現,如果加了volatile關健值會多一個lock指令,這個指令決定了可見性問題。

    1.什麼是可見性

      在單執行緒的環境下,如果向一個變數先寫入一個值,然後在沒有寫干涉的情況下讀取這個變數的值,那這個時候讀取到的這個變數的值應該是之前寫入的那個值。這本來是一個很正常的事情。但是在多執行緒環境下,讀和寫發生在不同的執行緒中的時候,可能會出現:讀執行緒不能及時的讀取到其他執行緒寫入的最新的值。這就是所謂的可見性

 

 

相關文章