並發操作中的3大問題:原子性問題,可見性問題,有序性問題
原子性:一個或者多個操作在 CPU 執行的過程中不被中斷的特性
可見性:一個線程對共享變量的修改,另外一個線程能夠立刻看到
有序性:程序執行的順序按照代碼的先后順序執行
問題產生的原因
線程切換帶來的原子性問題
案列:
假設為一個32位的變量賦值包括兩個過程:為低16位賦值,為高16位賦值。可能發生一種情況:當將低16位數值寫入之后,突然被中斷,而此時又有一個線程去讀取該變量的值,那么讀取到的就是錯誤的數據。
緩存導致的可見性問題
案例:
//線程1執行的代碼
int i = 0;
i = 10;
//線程2執行的代碼
j = i;
假若執行線程1的是CPU1,執行線程2的是CPU2。由上面的分析可知,當線程1執行 i =10這句時,會先把i的初始值加載到CPU1的高速緩存中,然后賦值為10,那么在CPU1的高速緩存當中i的值變為10了,卻沒有立即寫入到主存當中。此時線程2執行 j = i,它會先去主存讀取i的值並加載到CPU2的緩存當中,注意此時內存當中i的值還是0,那么就會使得j的值為0,而不是10。這就是可見性問題,線程1對變量i修改了之后,線程2沒有立即看到線程1修改的值。
編譯優化帶來的有序性問題
案列:
//線程1:
context = loadContext(); //語句1
inited = true; //語句2
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
由於語句1和語句2沒有數據依賴性,因此可能會被重排序。假如發生了指令重排序,在線程1執行過程中先執行語句2,而此是線程2會以為初始化工作已經完成,那么就會跳出while循環,去執行doSomethingwithconfig(context)方法,而此時context並沒有被初始化,就會導致程序出錯。
解決辦法
JDK Atomic開頭的原子類、synchronized、LOCK,可以解決原子性問題
synchronized、volatile、LOCK,可以解決可見性問題(volatile不能解決原子性問題,可以解決有序性問題)
synchronized、LOCK、volatile、Happens-Before 規則可以解決有序性問題