java并發(fā)編程關(guān)鍵字volatile保證可見性不保證原子性詳解
volatile關(guān)鍵字可以說是Java虛擬機(jī)提供的最輕量級的同步機(jī)制,但對于為什么它只能保證可見性,不保證原子性,它又是如何禁用指令重排的,還有很多同學(xué)沒徹底理解
相信我,堅(jiān)持看完這篇文章,你將牢牢掌握一個(gè)Java核心知識(shí)點(diǎn)
先說它的兩個(gè)作用:
保證變量在內(nèi)存中對線程的可見性禁用指令重排
每個(gè)字都認(rèn)識(shí),湊在一起就麻了
這兩個(gè)作用通常很不容易被我們Java開發(fā)人員正確、完整地理解,以至于許多同學(xué)不能正確地使用volatile
關(guān)于可見性
不多bb,碼來
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);
}
}
代碼很好理解,開了十個(gè)線程對同一個(gè)共享變量count做累加,每個(gè)線程累加1w次
count我們已經(jīng)用volatile修飾,已經(jīng)保證了count對十個(gè)線程在內(nèi)存中的可見性,按理說十個(gè)線程執(zhí)行完畢count的值應(yīng)該10w
然鵝,運(yùn)行多次,結(jié)果都遠(yuǎn)小于期望值

是哪個(gè)環(huán)節(jié)出了問題?

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

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

這不就妥了嘛
到現(xiàn)在,對于以下兩點(diǎn)你應(yīng)該有了新的認(rèn)知
volatile保證變量在內(nèi)存中對線程的可見性
volatile只保證可見性,不保證原子性
關(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)化
指令重排在大部分場景下確實(shí)能提升執(zhí)行效率,但有些場景對代碼執(zhí)行順序是強(qiáng)依賴的,此時(shí)我們需要禁用指令重排,如下面這個(gè)場景

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

這是個(gè)單例模式的實(shí)現(xiàn),下面是它的部分字節(jié)碼,紅框中 mov%eax,0x150(%esi) 是對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)存屏障前面,好好捋一下這段話
最后,留一個(gè)能加深大家對volatile理解的問題,兄弟們好好思考下:
Java代碼明明是從上往下依次執(zhí)行,為什么會(huì)出現(xiàn)指令重排這個(gè)問題?
以上就是java并發(fā)編程關(guān)鍵字volatile保證可見性不保證原子性詳解的詳細(xì)內(nèi)容,更多關(guān)于java并發(fā)編程關(guān)鍵字volatile的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中用POI實(shí)現(xiàn)將數(shù)據(jù)導(dǎo)出到Excel
這篇文章主要介紹了Java中用POI實(shí)現(xiàn)將數(shù)據(jù)導(dǎo)出到Excel,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
Java?HashTable與Collections.synchronizedMap源碼深入解析
HashTable是jdk?1.0中引入的產(chǎn)物,基本上現(xiàn)在很少使用了,但是會(huì)在面試中經(jīng)常被問到。本文就來帶大家一起深入了解一下Hashtable,需要的可以參考一下2022-11-11
Java 中的阻塞隊(duì)列從基礎(chǔ)到高級的深度解析
本文介紹了阻塞隊(duì)列的基本概念和實(shí)現(xiàn)方式,Java中的BlockingQueue接口及其主要實(shí)現(xiàn)類(ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue)也進(jìn)行了詳細(xì)介紹,感興趣的朋友跟隨小編一起看看吧2025-10-10
java實(shí)現(xiàn)統(tǒng)計(jì)字符串中字符及子字符串個(gè)數(shù)的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)統(tǒng)計(jì)字符串中字符及子字符串個(gè)數(shù)的方法,涉及java針對字符串的遍歷、判斷及運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
Spring Session實(shí)現(xiàn)分布式session的簡單示例
本篇文章主要介紹了Spring Session實(shí)現(xiàn)分布式session的簡單示例,具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-05-05
Java SpringMVC實(shí)現(xiàn)國際化整合案例分析(i18n)
本篇文章主要介紹了Java SpringMVC實(shí)現(xiàn)國際化整合案例分析(i18n),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
SpringBoot中web模版數(shù)據(jù)渲染展示的案例詳解
憑借 Spring Framework 的模塊、與你最喜歡的工具的大量集成以及插入你自己的功能的能力,Thymeleaf 是現(xiàn)代 HTML5 JVM Web 開發(fā)的理想選擇——盡管它還有更多功能,本文重點(diǎn)給大家介紹SpringBoot中web模版數(shù)據(jù)渲染展示,需要的朋友可以參考下2022-01-01

