Java關(guān)鍵字volatile詳析
volatile關(guān)鍵字關(guān)于先說(shuō)它的兩個(gè)作用:
- 保證變量在內(nèi)存中對(duì)線程的可見(jiàn)性
- 禁用指令重排
每個(gè)字都認(rèn)識(shí),湊在一起就麻了
這兩個(gè)作用通常很不容易被我們Java開(kāi)發(fā)人員正確、完整地理解,以至于許多同學(xué)不能正確地使用volatile
一、可見(jiàn)性
碼:
public class VolatileTest {
? ? private static volatile int count = 0;
? ? private static void increase() {
? ? ? ? count++;
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? for (int j = 0; j < 10000; j++) {
? ? ? ? ? ? ? ? ? ? increase();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }).start();
? ? ? ? }
?? ??? ?// 所有線程累加完成后輸出
? ? ? ? while (Thread.activeCount() > 2) Thread.yield();
? ? ? ? System.out.println(count);
? ? }
}代碼很好理解,開(kāi)了十個(gè)線程對(duì)同一個(gè)共享變量count做累加,每個(gè)線程累加1w次
count我們已經(jīng)用volatile修飾,已經(jīng)保證了count對(duì)十個(gè)線程在內(nèi)存中的可見(jiàn)性,按理說(shuō)十個(gè)線程執(zhí)行完畢count的值應(yīng)該10w
運(yùn)行多次,結(jié)果都遠(yuǎn)小于期望值

是哪個(gè)環(huán)節(jié)出了問(wèn)題?
你肯定聽(tīng)過(guò)一句話:volatile只保證可見(jiàn)性,不保證原子性
這句話就是答案,但是依舊很多人沒(méi)搞懂其中的奧秘
說(shuō)來(lái)話長(zhǎng)我長(zhǎng)話短說(shuō),簡(jiǎn)單來(lái)講就是 count++這個(gè)操作不是原子的,它是分三步進(jìn)行
- 從內(nèi)存讀取 count 的值
- 執(zhí)行 count + 1
- 將 count 的新值寫回
要徹底搞懂這個(gè)問(wèn)題,我們得從字節(jié)碼入手
下面是increase方法編譯后的字節(jié)碼

看不懂沒(méi)關(guān)系,我們一行一行來(lái)看:
GETSTATIC:讀取 count 的當(dāng)前值ICONST_1:將常量 1 加載到棧頂IADD:執(zhí)行+1PUTSTATIC:寫入count最新值
ICONST_1和IADD其實(shí)就是真正的++操作
關(guān)鍵點(diǎn)來(lái)了,volatile只能保證線程在GETSTATIC這一步拿到的值是最新的,但當(dāng)該線程執(zhí)行到下面幾行指令時(shí),這期間可能就有其它線程把count的值修改了,最終導(dǎo)致舊值把真正的新值覆蓋
所以,并發(fā)編程中,只靠volatile修飾共享變量是不可靠的,最終還是要通過(guò)對(duì)關(guān)鍵方法加鎖來(lái)保證線程安全
就如上面的demo,稍加修改就能實(shí)現(xiàn)真正的線程安全
最簡(jiǎn)單的,給increase方法加個(gè)synchronized (synchronized怎么實(shí)現(xiàn)線程安全的我就不啰嗦了,我以前講過(guò) synchronized底層實(shí)現(xiàn)原理)
? ? private synchronized static void increase() {
? ? ? ? ++count;
? ? }run幾下:

這不就妥了嘛
到現(xiàn)在,對(duì)于以下兩點(diǎn)你應(yīng)該有了新的認(rèn)知:
- volatile保證變量在內(nèi)存中對(duì)線程的可見(jiàn)性
- volatile只保證可見(jiàn)性,不保證原子性
二、關(guān)于指令重排
并發(fā)編程中,cpu自身和虛擬機(jī)為了提高執(zhí)行效率,都會(huì)采用指令重排(在保證不影響結(jié)果的前提下,將某些代碼亂序執(zhí)行)
- 關(guān)于cpu:為了從分利用cpu,實(shí)際執(zhí)行指令時(shí)會(huì)做優(yōu)化;
- 關(guān)于虛擬機(jī):在
HotSpot vm中,為了提升執(zhí)行效率,JIT(即時(shí)編譯)模式也會(huì)做指令優(yōu)化
指令重排在大部分場(chǎng)景下確實(shí)能提升執(zhí)行效率,但有些場(chǎng)景對(duì)代碼執(zhí)行順序是強(qiáng)依賴的,此時(shí)我們需要禁用指令重排,如下面這個(gè)場(chǎng)景

偽代碼取自《深入理解Java虛擬機(jī)》:
其描述的場(chǎng)景是開(kāi)發(fā)中常見(jiàn)配置讀取過(guò)程,只是我們?cè)谔幚砼渲梦募r(shí)一般不會(huì)出現(xiàn)并發(fā),所以沒(méi)有察覺(jué)這會(huì)有問(wèn)題。
試想一下,如果定義initialized變量時(shí)沒(méi)有使用volatile修飾,就可能會(huì)由于指令重排序的優(yōu)化,導(dǎo)致位于線程A中最后一條代碼“initialized=true”被提前執(zhí)行(這里雖然使用Java作為偽代碼,但所指的重排序優(yōu)化是機(jī)器級(jí)的優(yōu)化操作,提前執(zhí)行是指這條語(yǔ)句對(duì)應(yīng)的匯編代碼被提前執(zhí)行),這樣在線程B中使用配置信息的代碼就可能出現(xiàn)錯(cuò)誤,而volatile通過(guò)禁止指令重排則可以避免此類情況發(fā)生
禁用指令重排只需要將變量聲明為volatile,是不是很神奇
我們來(lái)看看volatile是如何實(shí)現(xiàn)禁用指令重排的:

這是個(gè)單例模式的實(shí)現(xiàn),下面是它的部分字節(jié)碼,紅框中 mov%eax,0x150(%esi) 是對(duì)instance賦值

可以看到,在賦值后,還執(zhí)行了 lock addl$0x0,(%esp) 指令,關(guān)鍵點(diǎn)就在這兒,這行指令相當(dāng)于此處設(shè)置了個(gè) 內(nèi)存屏障 ,有了內(nèi)存屏障后,cpu或虛擬機(jī)在指令重排時(shí)就不能把內(nèi)存屏障后面的指令提前到內(nèi)存屏障前面,好好捋一下這段話
到此這篇關(guān)于Java關(guān)鍵字volatile詳析的文章就介紹到這了,更多相關(guān)Java關(guān)鍵字volatile內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaFX實(shí)現(xiàn)簡(jiǎn)易時(shí)鐘效果(二)
這篇文章主要為大家詳細(xì)介紹了JavaFX實(shí)現(xiàn)簡(jiǎn)易時(shí)鐘效果的第二篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
Java web項(xiàng)目中的強(qiáng)制登錄功能實(shí)現(xiàn)代碼
本文給大家分享Java web項(xiàng)目中的強(qiáng)制登錄功能實(shí)現(xiàn)代碼,為了避免直接進(jìn)入項(xiàng)目中存在的頁(yè)面,使用filter過(guò)濾器,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
SpringBoot注冊(cè)Filter的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot注冊(cè)Filter的兩種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
修改jar包package目錄結(jié)構(gòu)操作方法
這篇文章主要介紹了修改jar包package目錄結(jié)構(gòu)操作方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-07-07
java導(dǎo)出excel 瀏覽器直接下載或者或以文件形式導(dǎo)出
這篇文章主要介紹了java導(dǎo)出excel 瀏覽器直接下載或者或以文件形式導(dǎo)出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java volatile關(guān)鍵字原理剖析與實(shí)例講解
volatile是Java提供的一種輕量級(jí)的同步機(jī)制,Java?語(yǔ)言包含兩種內(nèi)在的同步機(jī)制:同步塊(或方法)和?volatile?變量,本文將詳細(xì)為大家總結(jié)Java volatile關(guān)鍵字,通過(guò)詳細(xì)的代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07

