對於內存模型的三大特性:有序性、原子性、可見性。
大家都知道volatile能保證可見性和有序性但是不能保證原子性,但是為什么呢?
一、原子性、有序性、可見性
1、原子性:
(1)原子的意思代表着——“不可分”;
(2)在整個操作過程中不會被線程調度器中斷的操作,都可認為是原子性。原子性是拒絕多線程交叉操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。
2、可見性
線程執行結果在內存中對其它線程的可見性。
變量經過volatile修飾后,對此變量進行寫操作時,匯編指令中會有一個LOCK前綴指令,加了這個指令后,會引發兩件事情:
- 發生修改后強制將當前處理器緩存行的數據寫回到系統內存。
- 這個寫回內存的操作會使得在其他處理器緩存了該內存地址無效,重新從內存中讀取。
3、有序性
在本線程內觀察,所有操作都是有序的(即指令重排不會導致單線程程序執行結果與排序前有任何差別)。在一個線程觀察另一個線程,所有操作都是無序的,無序是因為發生了指令重排序。在 Java 內存模型中,允許編譯器和處理器對指令進行重排序,重排序過程不會影響到單線程程序的執行,卻會影響到多線程並發執行的正確性。
二、線程安全的兩個問題,執行控制和內存可見
執行控制(synchronize):控制代碼只能順序執行(執行一次只能被一個線程執行)或者可以多線程並發執行。
內存可見控制(volatile):線程執行結果在內存中對其它線程的可見性。線程在具體執行時,會先拷貝主存數據到線程本地(CPU緩存),操作完成后再把結果從線程本地刷到主存。
volatile和synchronize兩個關鍵字就是上述兩種作用。
- synchronize關鍵字使得同一時刻只有一個線程可以獲得當前變量、方法、類的鎖,其他線程無法訪問,也就無法同步並發執行,
synchronized
還會創建一個內存屏障,內存屏障指令保證了所有CPU操作結果都會直接刷到主存中,從而保證了操作的內存可見性,同時也使得先獲得這個鎖的線程的所有操作,都happens-before於隨后獲得這個鎖的線程的操作,保障有序性、可見性、原子性; - volatile通過強制將當前線程修改后的值寫回內存並使得其他線程中該值無效的方式保證其可見性,通過禁止指令重排的方式保證有序性,具體為何不能保證原子性在下一部分討論。
三、為什么volatile不能保證原子性
對於i=1這個賦值操作,由於其本身是原子操作,因此在多線程程序中不會出現不一致問題,但是對於i++這種復合操作,即使使用volatile關鍵字修飾也不能保證操作的原子性,可能會引發數據不一致問題。
private volatile int i = 0; i++;
如果啟了500條線程並發地去執行i++這個操作 最后的結果i是小於500的
i++操作可以被拆分為三步:
1,線程讀取i的值
2、i進行自增計算
3、刷新回i的值
網上一些博客的解釋是:
假設某一時刻i=5,此時有兩個線程同時從主存中讀取了i的值,那么此時兩個線程保存的i的值都是5, 此時A線程對i進行了自增計算,然后B也對i進行自增計算,此時兩條線程最后刷新回主存的i的值都是6(本來兩條線程計算完應當是7)所以說volatile保證不了原子性。
我的不解之處在於:
既然i是被volatile修飾的變量,那么對於i的操作應該是線程之間是可見的啊,就算A.,B兩個線程都同時讀到i的值是5,但是如果A線程執行完i的操作以后應該會把B線程讀到的i的值置為無效並強制B重新讀入i的新值也就是6然后才會進行自增操作才對啊。
后來參照其他博客終於想通了:
1、線程讀取i
2、temp = i + 1
3、i = temp
當 i=5 的時候A,B兩個線程同時讀入了 i 的值, 然后A線程執行了 temp = i + 1的操作, 要注意,此時的 i 的值還沒有變化,然后B線程也執行了 temp = i + 1的操作,注意,此時A,B兩個線程保存的 i 的值都是5,temp 的值都是6, 然后A線程執行了 i = temp (6)的操作,此時i的值會立即刷新到主存並通知其他線程保存的 i 值失效, 此時B線程需要重新讀取 i 的值那么此時B線程保存的 i 就是6,同時B線程保存的 temp 還仍然是6, 然后B線程執行 i=temp (6),所以導致了計算結果比預期少了1。
四、volatile和synchronized的區別
- volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
- volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
- volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
- volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
- volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化
參考文獻:
java內存模型:https://blog.csdn.net/suifeng3051/article/details/52611310
volatile與synchronize的區別:https://blog.csdn.net/it_manman/article/details/79497807
為什么volatile不能保證原子性:https://blog.csdn.net/xdzhouxin/article/details/81236356 https://blog.csdn.net/ACreazyCoder/article/details/82047970