首先要了解的是,volatile可以保證可見性和順序性,這些都很好理解,那么它為什么不能保證原子性呢?
可見性
可見性與Java的內存模型有關,模型采用緩存與主存的方式對變量進行操作,也就是說,每個線程都有自己的緩存空間,對變量的操作都是在緩存中進行的,之后再將修改后的值返回到主存中,這就帶來了問題,有可能一個線程在將共享變量修改后,還沒有來的及將緩存中的變量返回給主存中,另外一個線程就對共享變量進行修改,那么這個線程拿到的值是主存中未被修改的值,這就是可見性的問題。
volatile很好的保證了變量的可見性,變量經過volatile修飾后,對此變量進行寫操作時,匯編指令中會有一個LOCK前綴指令,這個不需要過多了解,但是加了這個指令后,會引發兩件事情:
將當前處理器緩存行的數據寫回到系統內存
這個寫回內存的操作會使得在其他處理器緩存了該內存地址無效什么意思呢?意思就是說當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值,這就保證了可見性。
原子性
問題來了,既然它可以保證修改的值立即能更新到主存,其他線程也會捕捉到被修改后的值,那么為什么不能保證原子性呢?
首先需要了解的是,Java中只有對基本類型變量的賦值和讀取是原子操作,如i = 1的賦值操作,但是像j = i或者i++這樣的操作都不是原子操作,因為他們都進行了多次原子操作,比如先讀取i的值,再將i的值賦值給j,兩個原子操作加起來就不是原子操作了。
所以,如果一個變量被volatile修飾了,那么肯定可以保證每次讀取這個變量值的時候得到的值是最新的,但是一旦需要對變量進行自增這樣的非原子操作,就不會保證這個變量的原子性了。
例:
一個變量i被volatile修飾,兩個線程想對這個變量修改,都對其進行自增操作也就是i++,i++的過程可以分為三步,首先獲取i的值,其次對i的值進行加1,最后將得到的新值寫會到緩存中。
線程A首先得到了i的初始值100,但是還沒來得及修改,就阻塞了,這時線程B開始了,它也得到了i的值,由於i的值未被修改,即使是被volatile修飾,主存的變量還沒變化,那么線程B得到的值也是100,之后對其進行加1操作,得到101后,將新值寫入到緩存中,再刷入主存中。根據可見性的原則,這個主存的值可以被其他線程可見。
問題來了,線程A已經讀取到了i的值為100,也就是說讀取的這個原子操作已經結束了,所以這個可見性來的有點晚,線程A阻塞結束后,繼續將100這個值加1,得到101,再將值寫到緩存,最后刷入主存,所以即便是volatile具有可見性,也不能保證對它修飾的變量具有原子性。
本文結束。
【轉載】https://blog.csdn.net/xdzhouxin/article/details/81236356