並發編程中常常繞不開原子性、可見性與有序性的討論。先來看看什麼是原子性?
什麼是原子性
原子(atomic)本意是不能被進一步分割的最小粒子,而原子操作意為不可被中斷的一個或者一系列操作。
volatile是不能保證原子性,但是在特定場景就是在32位處理器裡,對double和long型的變量的讀寫操作加了volatile修飾可以保證原子性。因為double和long都是64位,當JVM在32位處理器上運行時,可能會把64位的double/long型變量拆分為兩個32位的寫操作來執行,其他處理器可能讀到一個「寫了一半」的無效值,加了volatile就可以保證原子性。
內存屏障
可見性和有序性是基於各種內存屏障(禁止指令重排序)來實現的,先來看下有哪些內存屏障類型,以及可以解決那些因重排序引起的有序性問題。對有序性不太理解的同學可以先看下前這篇文章面試官:你知道happens-before規則嗎
本文的重點來了,對於volatile修復的變量,在它的讀/寫操作前後都會加上不同的內存屏障。
在每個volatile寫操作的前面插入一個StoreStore屏障,後面插入一個StoreLoad屏障,保證volatile寫與之前的寫操作指令不會重排序,寫完數據之後立即執行flush處理器緩存操作將所有寫操作刷到內存,對所有處理器可見。在每個volatilie讀操作的前面插入一LoadLoad屏障,保證在該變量讀操作時,如果其他處理修改過,必須從其他處理器高速緩存(或者主內存)加載到自己本地高速緩存,保證讀取到的值是最新的。然後在該變量讀操作後面插入一個LoadStore屏障,禁止volatile讀操作與後面任意讀寫操作重排序。
volatile底層實現可見性與有序性原理
1、關於實現有序性,主要通過對volatile修飾的變量的讀寫操作前後加上各種特定的內存屏障來禁止指令重排序來保障有序性的。
2、關於實現可見性,主要是通過 Lock前綴指令 + MESI緩存一致性協議來實現的。對volatiile修飾的變量執行寫操作時,JVM會發送一個Lock前綴指令給CPU,CPU在執行完寫操作後,會立即將新值刷新到內存,同時因為MESI緩存一致性協議,其他各個CPU都會對總線嗅探,看自己本地緩存中的數據是否被別人修改,如果發現修改了,會把自己本地緩存的數據過期掉。然後這個CPU裡的線程在讀取改變量時,就會從主內存裡加載最新的值了,這樣就保證了可見性。各位看官還可以參考這篇文章CPU是如何通過緩存一致性MESI協議來解決可見性的