詳述Java內(nèi)存屏障渗磅,透徹理解volatile

一般來說內(nèi)存屏障分為兩層:編譯器屏障和CPU屏障削彬,前者只在編譯期生效,目的是防止編譯器生成亂序的內(nèi)存訪問指令;后者通過插入或修改特定的CPU指令,在運行時防止內(nèi)存訪問指令亂序執(zhí)行。

下面簡單說一下這兩種屏障惋增。

1、編譯器屏障

編譯器屏障如下:

asm `volatile("": : :"memory"``

內(nèi)聯(lián)匯編時只是插入了一個空指令""改鲫,關(guān)鍵在在內(nèi)聯(lián)匯編中的修改寄存器列表中指定了"memory"诈皿,它告訴編譯器:這條指令(其實是空的)可能會讀取任何內(nèi)存地址,也可能會改寫任何內(nèi)存地址像棘。那么編譯器會變得保守起來稽亏,它會防止這條fence命令上方的內(nèi)存訪問操作移到下方,同時防止下方的操作移到上面缕题,也就是防止了亂序截歉,是我們想要的結(jié)果。這條命令還有另外一個副作用:它會讓編譯器把所有緩存在寄存器中的內(nèi)存變量刷新到內(nèi)存中烟零,然后重新從內(nèi)存中讀取這些值瘪松。

總結(jié)一下就是,如上命令有兩個作用锨阿,防止指令重排序以及保證可見性宵睦。

如果使用純字節(jié)碼解釋器來運行Java,那么HotSpot VM中orderAccess_linux_zero.inline.hpp文件中有如下實現(xiàn):

static inline void compiler_barrier() {

__asm__ volatile("": : :"memory");

}
inline void OrderAccess::loadload()   {
compiler_barrier(); 
}

inline void OrderAccess::storestore() {

compiler_barrier(); }

inline void OrderAccess::loadstore()  {

compiler_barrier(); }

這種方式依賴于編譯器達(dá)到目的時墅诡,如果編譯器支持壳嚎,就不用在不同的平臺和CPU上再專門編寫對應(yīng)的實現(xiàn),簡化了跨平臺操作。

2诬辈、x86 CPU屏障

x86屬于一個強(qiáng)內(nèi)存模型,這意味著在大多數(shù)情況下CPU會保證內(nèi)存訪問指令有序執(zhí)行荐吉。為了防止這種CPU亂序焙糟,我們需要添加CPU內(nèi)存屏障。X86專門的內(nèi)存屏障指令是"mfence"样屠,另外還可以使用lock指令前綴起到相同的效果穿撮,后者開銷更小。也就是說痪欲,內(nèi)存屏障可以分為兩類:

  • 本身是內(nèi)存屏障悦穿,比如“l(fā)fence”,“sfence”和“mfence”匯編指令
  • 本身不是內(nèi)存屏障业踢,但是被lock指令前綴修飾栗柒,其組合成為一個內(nèi)存屏障。在X86指令體系中知举,其中一類內(nèi)存屏障常使用“l(fā)ock指令前綴加上一個空操作”方式實現(xiàn)瞬沦,比如lock addl $0x0,(%esp)

下面介紹一下lock指令前綴。lock指令前綴功能如下:

  • 被修飾的匯編指令成為“原子的”
  • 與被修飾的匯編指令一起提供內(nèi)存屏障效果

在X86指令體系中雇锡,具有l(wèi)ock指令前綴逛钻,其內(nèi)允許使用lock指令前綴修飾的匯編指令有:


ADD,ADC,AND,BTC,BTR,BTS,CMPXCHG,CMPXCH8B,DEC,INC,NEG,NOT,OR,SBB,SUB,XOR,XADD,XCHG等

需要注意的是,“XCHG”和“XADD”匯編指令本身是原子指令锰提,但也允許使用lock指令前綴進(jìn)行修飾曙痘。

lock前綴的2個作用要記住。第一個是內(nèi)存屏障立肘,任何顯式或隱式帶有l(wèi)ock前綴的指令以及CPUID等指令都有內(nèi)存屏障的作用边坤。如xchg [mem], reg具有隱式的lock前綴。第二個是原子性谅年,單指令并不是一個不可分割的操作惩嘉,比如mov,本身只有其操作數(shù)滿足某些條件的時候才是原子的踢故,但是如果允許有l(wèi)ock前綴文黎,那就是原子的。

3殿较、HotSpot VM中的內(nèi)存屏障

JMM為了更好讓Java開發(fā)者獨立于CPU的方式理解這些概念耸峭,對內(nèi)存讀(Load)和寫(Store)操作進(jìn)行兩兩組合:LoadLoad、LoadStore淋纲、StoreLoad以及StoreStore劳闹,只有StoreLoad組合可能亂序,而且Store和Load的內(nèi)存地址必須是不一樣的。

現(xiàn)在只討論x86架構(gòu)下的CPU屏障本涕,參考的是Intel手冊业汰。4個屏障只是Java為了跨平臺而設(shè)計出來的,實際上根據(jù)CPU的不同菩颖,對應(yīng) CPU 平臺上的 JVM 可能可以優(yōu)化掉一些 屏障样漆,例如LoadLoad、LoadStore和StoreStore是x86上默認(rèn)就有的行為晦闰,在這個平臺上寫代碼時會簡化一些開發(fā)過程放祟。X86-64下僅支持一種指令重排:StoreLoad ,即讀操作可能會重排到寫操作前面呻右,同時不同線程的寫操作并沒有保證全局可見跪妥,例子見《Intel? 64 and IA-32 Architectures Software Developer’s Manual》手冊8.6.1、8.2.3.7節(jié)声滥。這個問題用lock或mfence解決眉撵,不能靠組合sfence和lfence解決。

JDK 1.8版本中的HotSpot VM在x86上實現(xiàn)的loadload()落塑、storestore()以及l(fā)oadstore()函數(shù)如下:

inline` `void` `OrderAccess::loadload(){

acquire();
}
inline void OrderAccess::storestore(){
release();
}

inline void OrderAccess::loadstore(){
acquire();
}
inline void OrderAccess::storeload(){

fence();
}
inline void OrderAccess::acquire() {
volatile intptr_t local_dummy;
#ifdef AMD64
__asm__ volatile ("movq 0(%%rsp), %0" : "=r" (local_dummy) : : "memory");
#else
__asm__ volatile ("movl 0(%%esp),%0" : "=r" (local_dummy) : : "memory");
#endif // AMD64
}

inline void OrderAccess::release() {
// Avoid hitting the same cache-line from different threads.
volatile jint local_dummy = 0;
}

acquire語義防止它后面的讀寫操作重排序到acquire前面执桌,所以LoadLoad和LoadStore組合后可滿足要求;release防止它前面的讀寫操作重排序到release后面芜赌,所以可由StoreStore和LoadStore組合后滿足要求仰挣。這樣acquire和release就可以實現(xiàn)一個"柵欄",禁止內(nèi)部讀寫操作跑到外邊缠沈,但是外邊的讀寫操作仍然可以跑到“柵欄”內(nèi)膘壶。

在x86上,acquire和release沒有涉及到StoreLoad洲愤,所以本來默認(rèn)支持颓芭,在函數(shù)實現(xiàn)時,完全可以不做任何操作柬赐。具體在實現(xiàn)時亡问,acquire()函數(shù)讀取了一個C++的volatile變量,而release()函數(shù)寫入了一個C++的volatile變量肛宋。這可能是支持微軟從Visual Studio 2005開始就對C++ volatile關(guān)鍵字添加了同步語義州藕,也就是對volatile變量的讀操作具有acquire語義,對volatile變量的寫操作具有release語義酝陈。

另外還可以順便說一下床玻,借助acquire與release語義可以實現(xiàn)互斥鎖(mutex),實際上沉帮,mutex正是acquire與release這兩個原語的由來锈死,acquire的本意是acquire a lock贫堰,release的本意是release a lock,因此待牵,互斥鎖能保證被鎖住的區(qū)域內(nèi)得到的數(shù)據(jù)不會是過期的數(shù)據(jù)其屏,而且所有寫入操作在release之前一定會寫入內(nèi)存。所以后續(xù)我們在實現(xiàn)鎖的過程中會有如下代碼出現(xiàn):

pthread_mutex_lock(&mutex);
// 操作
pthread_mutex_unlock(&mutex);
OrderAccess::storeload()函數(shù)調(diào)用的fence()的實現(xiàn)如下:
inline void OrderAccess::fence() {
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
}

可以看到是使用lock前綴來解決內(nèi)存屏障問題缨该。

下面看一下Java的volatile變量的實現(xiàn)偎行。

字節(jié)碼層面會在access_flags中會標(biāo)記某個屬性為volatitle,到HotSpot VM后压彭,對volatitle內(nèi)存區(qū)進(jìn)行讀寫時,都加屏障渗常,如讀取volatile變量時加如下屏障:

volatile變量讀操作
LoadLoad
LoadStore

在寫volatilie變量時加如下屏障:

LoadStore
StoreStore
volatile變量寫操作
StoreLoad

如上的volatile變量讀之后的操作不允許重排序到前面壮不,而寫之前的操作也不允許重排序到寫后面,所以volatile有acquire和release的語義皱碘。

對x86-64位來說询一,只需要對StoreLoad進(jìn)行處理,所以從解釋執(zhí)行的putfield或putstatic指令來看癌椿,會在最后寫入volatilie變量后加如下指令:

lock addl $0x0,(%rsp)

在多線程編程中健蕊,由于使用互斥量,信號量和事件都在設(shè)計的時候都阻止了它們調(diào)用點中的內(nèi)存亂序(已經(jīng)隱式包含各種memery barrier)踢俄,內(nèi)存亂序的問題同樣不需要考慮了缩功。只有當(dāng)使用無鎖(lock-free)技術(shù)時–內(nèi)存在線程間共享而沒有任何的互斥量,內(nèi)存亂序的效果才會顯露無疑都办,這樣我們才需要考慮在合適的地方加入合適的memery barrier嫡锌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市琳钉,隨后出現(xiàn)的幾起案子势木,更是在濱河造成了極大的恐慌,老刑警劉巖歌懒,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啦桌,死亡現(xiàn)場離奇詭異,居然都是意外死亡及皂,警方通過查閱死者的電腦和手機(jī)甫男,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來验烧,“玉大人查剖,你說我怎么就攤上這事≡刖剑” “怎么了笋庄?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵效扫,是天一觀的道長。 經(jīng)常有香客問我直砂,道長菌仁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任静暂,我火速辦了婚禮济丘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洽蛀。我一直安慰自己摹迷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布郊供。 她就那樣靜靜地躺著峡碉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驮审。 梳的紋絲不亂的頭發(fā)上鲫寄,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音疯淫,去河邊找鬼地来。 笑死,一個胖子當(dāng)著我的面吹牛熙掺,可吹牛的內(nèi)容都是我干的未斑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼币绩,長吁一口氣:“原來是場噩夢啊……” “哼颂碧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起类浪,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤载城,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后费就,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诉瓦,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年力细,在試婚紗的時候發(fā)現(xiàn)自己被綠了睬澡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡眠蚂,死狀恐怖煞聪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逝慧,我是刑警寧澤昔脯,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布啄糙,位于F島的核電站,受9級特大地震影響云稚,放射性物質(zhì)發(fā)生泄漏隧饼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一静陈、第九天 我趴在偏房一處隱蔽的房頂上張望燕雁。 院中可真熱鬧,春花似錦鲸拥、人聲如沸拐格。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捏浊。三九已至,卻和暖如春角撞,著一層夾襖步出監(jiān)牢的瞬間呛伴,已是汗流浹背勃痴。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工谒所, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沛申。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓劣领,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铁材。 傳聞我的和親對象是個殘疾皇子尖淘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容