上一篇:Java併發程式設計(五)Thread啟動原始碼分析
一、原子性
原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
一個很經典的例子就是銀行賬戶轉賬問題:
比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。
試想一下,如果這2個操作不具備原子性,會造成什麼樣的後果。假如從賬戶A減去1000元之後,操作突然中止。然後又從B取出了500元,取出500元之後,再執行 往賬戶B加上1000元 的操作。這樣就會導致賬戶A雖然減去了1000元,但是賬戶B沒有收到這個轉過來的1000元。
所以這2個操作必須要具備原子性才能保證不出現一些意外的問題。
同樣地反映到併發程式設計中會出現什麼結果呢?
舉個最簡單的例子,大家想一下假如為一個32位的變數賦值過程不具備原子性的話,會發生什麼後果?
i = 9;
假若一個執行緒執行到這個語句時,我暫且假設為一個32位的變數賦值包括兩個過程:為低16位賦值,為高16位賦值。
那麼就可能發生一種情況:當將低16位數值寫入之後,突然被中斷,而此時又有一個執行緒去讀取i的值,那麼讀取到的就是錯誤的資料。
二、可見性
可見性是指當一個執行緒修改了共享變數後,其他執行緒能夠立即得知這個修改;
如:執行緒1和執行緒2同時載入變數flag=1到自己的本地記憶體中來,但是2個執行緒在不同的cpu執行此時可能就會出現執行緒1將flag修改為2,但是這個時候只是在自己的工作記憶體中,或者說cpu的快取記憶體中,還沒來得及寫入主記憶體,也有可能寫入了主記憶體,但是執行緒2沒有及時去重新整理主記憶體的資料,這個時候就會導致執行緒2讀取的flag還是為1;
這個就是併發程式設計中比較常見的可見性問題,後續會講解volatile如何解決可見性問題的;
三、有序性
編譯器和指令器,有的時候為了提高程式碼執行效率,會將指令重排序;
public class Singleton {
private Singleton() { }
private volatile static Singleton instance;
public Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
instance = new Singleton();
這條語句實際上包含了三個操作:1.分配物件的記憶體空間;2.初始化物件;3.設定instance指向剛分配的記憶體地址。但由於存在重排序的問題,可能有以下的執行順序:
如果2和3進行了重排序的話,執行緒B進行判斷if(instance==null)時就會為true,而實際上這個instance並沒有初始化成功,顯而易見對執行緒B來說之後的操作就會是錯得。而用volatile修飾的話就可以禁止2和3操作重排序,從而避免這種情況。volatile包含禁止指令重排序的語義,其具有有序性。