Java之volatile如何保證可見(jiàn)性和指令重排序

1 我們先了解CPU緩存

CPU緩存為了解決CPU運(yùn)算速度與內(nèi)存讀寫(xiě)速度不匹配的問(wèn)題,因?yàn)镃PU運(yùn)算速度要比內(nèi)存讀寫(xiě)速度快得多

    一次主內(nèi)存的訪(fǎng)問(wèn)通常在幾十到幾百個(gè)時(shí)鐘周期
    一次L1高速緩存的讀寫(xiě)只需要1~2個(gè)時(shí)鐘周期
    一次L2高速緩存的讀寫(xiě)也只需要數(shù)十個(gè)時(shí)鐘周期

CPU大多數(shù)情況下讀寫(xiě)都不會(huì)直接訪(fǎng)問(wèn)內(nèi)存,取而代之的是CPU緩存,CPU緩存是位于CPU與內(nèi)存之間的臨時(shí)存儲(chǔ)器(簡(jiǎn)單理解為寄存器),它容量比內(nèi)存小得多但是交換速度卻比內(nèi)存快得多。而緩存中的數(shù)據(jù)是內(nèi)存中的一小部分?jǐn)?shù)據(jù),但這一小部分是短時(shí)間內(nèi)CPU即將訪(fǎng)問(wèn)的,當(dāng)CPU調(diào)用大量數(shù)據(jù)時(shí),就可先從緩存中讀取,從而加快讀取速度

CPU緩存可分為:一級(jí)緩存(是與CPU結(jié)合最為緊密的CPU緩存)、二級(jí)緩存、三級(jí)緩存,每一級(jí)緩存中所存儲(chǔ)的數(shù)據(jù)全部都是下一級(jí)緩存中的一部分

當(dāng)CPU要讀取數(shù)據(jù)時(shí),首先從一級(jí)緩存中查找,如果沒(méi)有再?gòu)亩?jí)緩存中查找,如果還是沒(méi)有再?gòu)娜?jí)緩存中或內(nèi)存中查找。一般來(lái)說(shuō)每級(jí)緩存的命中率大概都有80%左右,只剩下20%的總數(shù)據(jù)量才需要從二級(jí)緩存、三級(jí)緩存或內(nèi)存中讀取。

CPU執(zhí)行計(jì)算的過(guò)程如下:

    程序以及數(shù)據(jù)被加載到主內(nèi)存
    指令和數(shù)據(jù)被加載到CPU緩存
    CPU執(zhí)行指令,把結(jié)果寫(xiě)到高速緩存
    高速緩存中的數(shù)據(jù)寫(xiě)回主內(nèi)存


 
2 總線(xiàn)鎖

每個(gè)CPU都有一級(jí)緩存,但是,我們卻無(wú)法保證每個(gè)CPU的一級(jí)緩存數(shù)據(jù)都是一樣的,如何保證各個(gè)CPU緩存中的數(shù)據(jù)是一致的。就是CPU的緩存一致性問(wèn)題

1)總線(xiàn)鎖

一種處理一致性問(wèn)題的辦法是使用Bus Locking(總線(xiàn)鎖)。當(dāng)一個(gè)CPU對(duì)其緩存中的數(shù)據(jù)進(jìn)行操作的時(shí)候,往總線(xiàn)中發(fā)送一個(gè)Lock信號(hào)。 這個(gè)時(shí)候,所有CPU收到這個(gè)信號(hào)之后就不操作自己緩存中的對(duì)應(yīng)數(shù)據(jù)了,也就是把數(shù)據(jù)直接寫(xiě)入主內(nèi)存,當(dāng)操作結(jié)束,釋放鎖以后,所有的CPU就去內(nèi)存中獲取最新數(shù)據(jù)更新。


 
3 volatile如何保證可見(jiàn)性

我們把有volatile修飾的變量編譯成部分匯編,這里有個(gè)lock指令

0x01a3de24: lock addl $0X0,(%esp);

如果是寫(xiě)操作,cpu會(huì)發(fā)出一個(gè)lock指令,CUP會(huì)把數(shù)據(jù)直接寫(xiě)到到主內(nèi)存

如果是讀操作,cpu會(huì)發(fā)出一個(gè)unlock指令, 所有的CPU就去內(nèi)存中獲取最新數(shù)據(jù)更新


 
4 volatile如何保證指令重排序

現(xiàn)代的操作系統(tǒng)都是多處理器.而每一個(gè)處理器都有自己的緩存,并且這些緩存并不是實(shí)時(shí)都與內(nèi)存發(fā)生信息交換.這樣就可能出現(xiàn)一個(gè)cpu上的緩存數(shù)據(jù)與另一個(gè)cpu上的緩存數(shù)據(jù)不一致的問(wèn)題.而這樣在多線(xiàn)程開(kāi)發(fā)中,就有可能導(dǎo)致出現(xiàn)一些異常行為.
而操作系統(tǒng)底層為了這些問(wèn)題,提供了一些內(nèi)存屏障用以解決這樣的問(wèn)題.目前有4種屏障.

    LoadLoad屏障:對(duì)于這樣的語(yǔ)句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪(fǎng)問(wèn)前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
    StoreStore屏障:對(duì)于這樣的語(yǔ)句Store1; StoreStore; Store2,在Store2及后續(xù)寫(xiě)入操作執(zhí)行前,保證Store1的寫(xiě)入操作對(duì)其它處理器可見(jiàn)。
    LoadStore屏障:對(duì)于這樣的語(yǔ)句Load1; LoadStore; Store2,在Store2及后續(xù)寫(xiě)入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
    StoreLoad屏障:對(duì)于這樣的語(yǔ)句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫(xiě)入對(duì)所有處理器可見(jiàn)。

在每個(gè)volatile寫(xiě)操作前插入StoreStore屏障,在寫(xiě)操作后插入StoreLoad屏障;
在每個(gè)volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障;

由于內(nèi)存屏障的作用,避免了volatile變量和其它指令重排序



作者:chen.yu
深信服三年半工作經(jīng)驗(yàn),目前就職游戲廠(chǎng)商,希望能和大家交流和學(xué)習(xí),
微信公眾號(hào):編程入門(mén)到禿頭 或掃描下面二維碼
零基礎(chǔ)入門(mén)進(jìn)階人工智能(鏈接)