可見性:
在單核時代,所有的線程都是在一顆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