Java關鍵字Volatile

java關鍵字Volatile用于將java變量標記為存儲在主內(nèi)存中,這就意味著每次讀取Volatile修飾的變量時都是從計算機的主內(nèi)存中讀取乃沙,而不是從CPU的緩存中讀取,并且每次對Volatile變量寫的時候都將寫入主內(nèi)存吼渡,而不僅僅是CPU緩存

可見性問題

Java關鍵字Volatile保證可以跨線程查看變量的變化,下面詳細來說一下這個問題
在線程操作非Volatile變量的多線程應用程序中壁却,出于性能原因,每個線程可以在處理它們時將變量從主內(nèi)存復制到CPU高速緩存中寻定。如果您的計算機包含多個CPU,則每個線程可以在不同的CPU上運行精耐。這意味著狼速,每個線程可以將變量復制到不同CPU的CPU緩存中。這在這里說明:



對于非Volatile變量卦停,java虛擬機從主內(nèi)存讀取到CPU緩存向胡,或從CPU緩存讀取到主內(nèi)存,這可能導致一系列問題:
假設兩個線程或者多個線程訪問一個共享對象惊完,共享對象包含一個counter變量僵芹,像這樣:

public class SharedObject {
    public int counter = 0;
}

假設只有線程1遞增counter變量,但線程1和線程2都可能時不時讀取counter變量小槐。
如果counter變量沒有聲明為Volatile拇派,則無法保證counter從CPU緩存讀取到主內(nèi)存中的具體時間,這就意味著CPU緩存中的變量值可能跟主內(nèi)存中變量值不同,這種情況如下所示:



因為還沒有被線程寫入主線程凿跳,而導致另一個線程沒有看到變量的最新值得問題件豌,被稱為線程的“可見性”問題
,線程的更新操作對其他線程不可見

Java Volatile可見性保證

java Volatile關鍵幀意在解決線程的可見性問題控嗜,通過對counter聲明volatile茧彤,對象counter所有寫的操作將立即寫入主內(nèi)存,同時對counter的讀操作也是直接訪問主內(nèi)存

public class SharedObject {
    public volatile int counter = 0;
}

因此聲明一個Volatile疆栏,可以保證對其他線程的可見性
在上面給出的場景中曾掂,一個線程(T1)修改計數(shù)器,另一個線程(T2)讀取計數(shù)器(但從不修改它)壁顶,聲明了volatile的counter足以保證T2對counter變量寫入的可見性珠洗。
但是,如果T1和T2都在增加counter變量若专,那么 counter變量聲明volatile就不夠了险污。稍后會詳細介紹。

volatile完全可見性保證

實際上富岳,volatile的可見性保證超出了volatile變量本身,可見性保證如下:
1.如果線程A 對volatile變量進行寫操作蛔糯,那么線程B可以立刻讀取相同的volatile變量,在對volatile變量寫前窖式,所有的變量對線程A都是可見的蚁飒。在讀取volatile變量后對線程B同樣是可見的。
2.如果線程A讀取volatile變量萝喘,則讀取變量時線程A的所有可見volatile變量也將從主內(nèi)存重新讀取
用實例代碼來說明:

public class MyClass {
    private int years;
    private int months
    private volatile int days;
    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

改update寫入三個變量淮逻,只有days用了volatile
volatile完全可見性意味著琼懊,當對days進行寫入時,線程所有的可見變量都會寫入主內(nèi)存(不僅僅是volatile變量自己寫入到主存中爬早,其他被該線程修改的所有變量也會刷新到主存)哼丈,這就意味著,當對days進行寫入時筛严,years和months也將寫入主內(nèi)存,
當對years醉旦、months和days讀取時,你可以這么做

public class MyClass {
    private int years;
    private int months
    private volatile int days;

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }

注意totalDays從讀取days的值開始到獲取total桨啃,當讀取days時车胡,years和months也會讀取到主內(nèi)存中,因此可以保證讀取到days照瘾、years和months的最新值

指令重排

只要指令的語義含義不變匈棘,jvm和CPU就可以出于性能的原因,重新排序指令的程序析命,看以下說明:

int a = 1;
int b = 2;
a++;
b++;

這些指令可以按一下方式重排主卫,而不會丟失程序的語義含義:

int a = 1;
a++;

int b = 2;
b++;

然而,當程序中有一個volatile字段時鹃愤,指令重新排序提出了挑戰(zhàn)队秩。讓我們從MyClass類中看看一下java volatile教程

public class MyClass {
    private int years;
    private int months
    private volatile int days;

    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

一旦update方法寫入一個days值,years和months也會被寫入主內(nèi)存中昼浦,但是如果jvm把指令重排序了

public void update(int years, int months, int days){
    this.days   = days;
    this.months = months;
    this.years  = years;
}

當days值被修改時馍资,months和years仍然被寫入主內(nèi)存,但是這一次days的值的改變在months和years寫入之前关噪,因此新值沒法正確的對其他線程可見鸟蟹,指令重排的語義以前被改變

Java volatile Happens-Before 保證

happens-before 關系是程序語句之間的排序保證,這能確保任何內(nèi)存的寫使兔,對其他語句都是可見的建钥。
為了解決指令重排挑戰(zhàn),volatile除了可見性保證之外虐沥,Java 關鍵字還提供“Happens-Before Guarantee”規(guī)則熊经。"Happens-Before"保證:
如果線程A寫入一個volatile變量,隨后線程B讀取相同的變量欲险。那么變量對線程A來說在寫入變量前就是可見的镐依,對于B來說讀取完變量后,對該變量也是可見的天试。
volatile變量的讀和寫指令不能由JVM重新排序()槐壳。讀寫指令前后可以重排序,但是volatile讀和寫不能與這些指令混合喜每。無論什么指令都應該在volatile變量讀寫之后务唐。

volatile并不足夠解決所有問題

volatile雖然能滿足直接把數(shù)據(jù)寫入主內(nèi)存并且直接從主內(nèi)存中取出雳攘,仍然存在不足的情況
在前面解釋的情況中,只有線程1寫入共享counter變量枫笛,聲明該counter變量volatile足以確保線程2始終看到最新的寫入值吨灭。
實際上,如果寫入volatile變量的新值不依賴于其先前的值刑巧,則多個線程甚至可以寫入共享變量喧兄,并且仍然具有存儲在主存儲器中的正確值。換句話說海诲,如果將值寫入共享volatile變量的線程首先不需要讀取其值來計算其下一個值繁莹。
一旦線程需要首先讀取volatile變量的值檩互,并且基于該值為共享volatile變量生成新值特幔,volatile變量就不再足以保證正確的可見性。讀取volatile 變量和寫入新值之間的短時間間隔會產(chǎn)生競爭條件 闸昨,其中多個線程可能讀取volatile變量的相同值蚯斯,為變量生成新值,并在將值寫回時主存 - 覆蓋彼此的值饵较。
多個線程遞增相同計數(shù)器的情況恰好是 volatile變量不夠的情況拍嵌。以下部分更詳細地解釋了這種情況。

想象一下循诉,如果線程1將counter值為0 的共享變量讀入其CPU高速緩存横辆,則將其增加到1并且不將更改的值寫回主存儲器。然后茄猫,線程2可以counter從主存儲器讀取相同的變量狈蚤,其中變量的值仍為0,進入其自己的CPU高速緩存划纽。然后脆侮,線程2也可以將計數(shù)器遞增到1,也不將其寫回主存儲器勇劣。這種情況如下圖所示:


線程1和線程2現(xiàn)在幾乎不同步靖避。共享counter變量的實際值應為2,但每個線程的CPU緩存中的變量值為1比默,而主存中的值仍為0.這是一個混亂幻捏!即使線程最終將共享counter變量的值寫回主存儲器,該值也將是錯誤的命咐。

volatile在什么時候使用

正如我前面提到的粘咖,如果兩個線程都在讀取和寫入共享變量,那么使用 volatile關鍵字是不夠的侈百。 在這種情況下瓮下,您需要使用synchronized來保證變量的讀取和寫入是原子性翰铡。讀取或?qū)懭雟olatile變量不會阻止線程讀取或?qū)懭搿榇朔砘担仨氃陉P鍵部分周圍使用synchronized 關鍵字锭魔。

作為synchronized塊的替代方法,您還可以使用java.util.concurrent包中找到的眾多原子數(shù)據(jù)類型之一路呜。例如迷捧,AtomicLong或者 AtomicReference其他更多。

如果只有一個線程讀取和寫入volatile變量的值胀葱,而其他線程只讀取變量漠秋,那么讀取線程將保證看到寫入volatile變量的最新值。則可以使用volatile關鍵詞

volatile關鍵字適用于32位和64位變量抵屿。

volatile的性能因素

volatile變量會導致變量讀取和寫入主內(nèi)存庆锦。讀取和寫入主內(nèi)存比訪問CPU緩存更昂貴。訪問volatile變量也會阻止指令重新排序轧葛,這是一種正常的性能增強技術搂抒。因此,在真正需要強制實施變量可見性時尿扯,應該只使用volatile變量求晶。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市衷笋,隨后出現(xiàn)的幾起案子芳杏,更是在濱河造成了極大的恐慌,老刑警劉巖辟宗,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爵赵,死亡現(xiàn)場離奇詭異,居然都是意外死亡慢蜓,警方通過查閱死者的電腦和手機亚再,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晨抡,“玉大人氛悬,你說我怎么就攤上這事≡胖” “怎么了如捅?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長调煎。 經(jīng)常有香客問我镜遣,道長,這世上最難降的妖魔是什么士袄? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任悲关,我火速辦了婚禮谎僻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寓辱。我一直安慰自己艘绍,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布秫筏。 她就那樣靜靜地躺著诱鞠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪这敬。 梳的紋絲不亂的頭發(fā)上航夺,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音崔涂,去河邊找鬼阳掐。 笑死,一個胖子當著我的面吹牛堪伍,可吹牛的內(nèi)容都是我干的锚烦。 我是一名探鬼主播觅闽,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼帝雇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛉拙?” 一聲冷哼從身側(cè)響起尸闸,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孕锄,沒想到半個月后吮廉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡畸肆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年宦芦,在試婚紗的時候發(fā)現(xiàn)自己被綠了咏连。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片情竹。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖幼苛,靈堂內(nèi)的尸體忽然破棺而出大咱,到底是詐尸還是另有隱情恬涧,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布碴巾,位于F島的核電站溯捆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏厦瓢。R本人自食惡果不足惜提揍,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一啤月、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劳跃,春花似錦顽冶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贸人,卻和暖如春间景,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艺智。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工倘要, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人十拣。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓封拧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夭问。 傳聞我的和親對象是個殘疾皇子泽西,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

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