防重排序
我們從一個最經(jīng)典的例子來分析重排序問題。大家應該都很熟悉單例模式的實現(xiàn),而在并發(fā)環(huán)境下的單例實現(xiàn)方式,我們通??梢圆捎秒p重檢查加鎖(DCL)的方式來實現(xiàn)。
其源碼如下:
現(xiàn)在我們分析一下為什么要在變量singleton之間加上volatile關鍵字。要理解這個問題,先要了解對象的構造過程,實例化一個對象其實可以分為三個步驟:
分配內存空間。 初始化對象。 將內存空間的地址賦值給對應的引用。
但是由于操作系統(tǒng)可以對指令進行重排序,所以上面的過程也可能會變成如下過程:
分配內存空間。 將內存空間的地址賦值給對應的引用。 初始化對象
如果是這個流程,多線程環(huán)境下就可能將一個未初始化的對象引用暴露出來,從而導致不可預料的結果。因此,為了防止這個過程的重排序,我們需要將變量設置為volatile類型的變量。
實現(xiàn)可見性
可見性問題主要指一個線程修改了共享變量值,而另一個線程卻看不到。引起可見性問題的主要原因是每個線程擁有自己的一個高速緩存區(qū)——線程工作內存。
volatile關鍵字能有效的解決這個問題,我們看下下面的例子,就可以知道其作用:
直觀上說,這段代碼的結果只可能有兩種:b=3;a=3 或 b=2;a=1。不過運行上面的代碼(可能時間上要長一點),你會發(fā)現(xiàn)除了上兩種結果之外,還出現(xiàn)了另外兩種結果:
為什么會出現(xiàn)b=2;a=3和b=3;a=1這種結果呢? 正常情況下,如果先執(zhí)行change方法,再執(zhí)行print方法,輸出結果應該為b=3;a=3。相反,如果先執(zhí)行的print方法,再執(zhí)行change方法,結果應該是 b=2;a=1。那b=3;a=1的結果是怎么出來的? 原因就是個線程將值a=3修改后,但是對第二個線程是不可見的,所以才出現(xiàn)這一結果。如果將a和b都改成volatile類型的變量再執(zhí)行,則再也不會出現(xiàn)b=2;a=3和b=3;a=1的結果了。
保證原子性:單次讀/寫
volatile不能保證完全的原子性,只能保證單次的讀/寫操作具有原子性。