天天看點

并發程式設計bug的源頭,可見性,原子性,有序性

可見性:

  在單核時代,所有的線程都是在一顆cpu上執行的,操作的都是同一個CPU的緩存,一個線程的寫,對甯一個線程一定是可見的。在多核時代,每顆cpu都有自己的緩存,當多個線程在不同的CPU上執行的時候,線程A操作的是CPU1上的緩存,線程B操作的是CPU2上的緩存,在操作共享變量的時候,A的操作對B是不具備可見性的,例如兩個線程同時操作變量int a =1,分别執行a+1操作,當兩個線程同時讀取a=1,到自己的緩存,進行加1操作,同時再寫入記憶體,我們發現記憶體中的值是2,而不是我們期望的3,這就是緩存的可見性問題

原子性:

   我們把一個或多個操作在CPU執行的過程中不被中斷的特性稱為原子性。當多線程在并行執行的時候,其實是一個線程執行一段時間之後,另外一個線程在執行一段時間,由于中間時間切換的時間很短,就以為兩個線程是并行執行的,我們稱這種為任務切換,切換的時間,例如50毫秒,稱為“時間片“.

   在java語言中,進階語言的一條語句往往需要多條cpu指令來完成,例如a+=1,就需要分3條cpu指令完成

   1.把a從記憶體加載到cpu的寄存器

    2.在寄存器中進行a+1操作

    3.結果寫回記憶體

    在多線程執行的時候,例如a=1,當線程把a讀到寄存器之後,任務切換了,這時候線程b也把a=1,讀到了自己的寄存器裡,兩個線程執行的結果就是2,而不是期望的3,這就導緻bug

  有序性

    編譯器為了優化性能,往往會改成程式的執行順序,例如a=7,a=8,優化之後會變成a=8,a=7,調整了語句的順序,但不會影響最終結果。但在多線程下編譯器優化會導緻bug

  例如,在java中的雙重檢查建立單例對象的時候

public static Singleton getInstanceDC() {
 2     if (_instance == null) {                // Single Checked
 3         synchronized (Singleton.class) {
 4             if (_instance == null) {        // Double checked
 5                 _instance = new Singleton();
 6             }
 7         }
 8     }
 9     return _instance;
10 }      

當執行_instance = new Singleton() 這句話的時候由于編譯器優化

執行順序變成 1.配置設定一塊記憶體M  2.将M的位址指派給Singleton對象 3.在記憶體M上初始化對象

單我們以為的順序應該是 1.配置設定一塊記憶體M  2.在記憶體M上初始化對象 3.将M的位址指派給Singleton對象 

編譯器優化之後,當多線程執行上面的代碼的時候,線程A執行到new  Singleton(),在第二部的時候進行線程切換,線程B開始執行,線程B執行到第一個_instance == null的時候,不為null了,是以就直接傳回了instance,而此時instance是沒有初始化的,當通路instance的成員變量的時候就會報空指針異常。這就是編譯器優化重排序,在多線程環境下出的bug

繼續閱讀