java中volatile關(guān)鍵字解釋說明
在 Java 中,volatile 是一個輕量級的同步機(jī)制關(guān)鍵字,用于修飾字段(變量),其核心作用是:
保證變量的“可見性”和“禁止指令重排序”,但不保證原子性。
下面我們從原理層面詳細(xì)解析 volatile 的工作機(jī)制。
一、三大特性:可見性、有序性、原子性
| 特性 | volatile 是否支持 | 說明 |
|---|---|---|
| 可見性(Visibility) | ? 支持 | 一個線程修改了 volatile 變量,其他線程能立即看到最新值 |
| 有序性(Ordering) | ? 支持(禁止重排序) | JVM 和 CPU 不會對 volatile 讀寫進(jìn)行重排序優(yōu)化 |
| 原子性(Atomicity) | ? 不支持(除 long/double 的簡單讀寫) | 如 i++ 這類復(fù)合操作不是原子的 |
二、volatile 的底層原理(基于內(nèi)存模型)
1.Java 內(nèi)存模型(JMM)背景
- 每個線程有自己的工作內(nèi)存(緩存、寄存器)
- 所有變量存儲在主內(nèi)存中
- 線程對變量的操作必須先從主內(nèi)存拷貝到工作內(nèi)存,操作后再寫回
問題:如果沒有同步機(jī)制,線程 A 修改了變量,線程 B 可能永遠(yuǎn)看不到新值(因?yàn)樽x的是本地緩存)。
2.volatile 如何解決可見性?
當(dāng)一個字段被聲明為 volatile:
- 寫操作:線程必須將該變量的最新值立即刷新到主內(nèi)存
- 讀操作:線程必須從主內(nèi)存重新讀取該變量的值,而不是使用本地緩存
這相當(dāng)于每次讀寫都強(qiáng)制與主內(nèi)存同步。
3.內(nèi)存屏障(Memory Barrier / Memory Fence)
JVM 在編譯 volatile 讀寫時,會插入內(nèi)存屏障指令,實(shí)現(xiàn)兩個效果:
(1)禁止指令重排序
- 在 volatile 寫之前的操作,不能重排到寫之后
- 在 volatile 讀之后的操作,不能重排到讀之前
例如:
// 假設(shè) flag 是 volatile a = 1; // 普通寫 flag = true; // volatile 寫
→ a = 1 一定發(fā)生在 flag = true 之前,不會被重排序。
(2)強(qiáng)制刷新緩存
- 寫屏障(Store Barrier):確保寫入主內(nèi)存
- 讀屏障(Load Barrier):確保從主內(nèi)存加載
這些屏障由 JVM 根據(jù)不同 CPU 架構(gòu)(x86、ARM 等)生成對應(yīng)的底層指令(如 lock 前綴指令)。
三、典型應(yīng)用場景
? 場景 1:狀態(tài)標(biāo)志位(最常見)
public class TaskRunner {
private volatile boolean running = true;
public void stop() {
running = false; // 其他線程能立即看到
}
public void run() {
while (running) {
// do work
}
}
}若不用 volatile,run() 方法可能因讀取本地緩存而永遠(yuǎn)無法退出。
? 場景 2:單例模式中的雙重檢查鎖定(DCL)
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 防止重排序?qū)е挛闯跏蓟瓿删捅灰?
}
}
}
return instance;
}
}如果沒有
volatile,new Singleton()的三步(分配內(nèi)存、初始化、賦值)可能被重排序?yàn)?ldquo;分配 → 賦值 → 初始化”,導(dǎo)致其他線程拿到未初始化的對象。
? 不適用場景:復(fù)合操作(非原子)
private volatile int count = 0;
public void increment() {
count++; // 實(shí)際是:讀取 count → +1 → 寫回,非原子!
}此時應(yīng)使用 AtomicInteger 或加鎖。
四、與 synchronized 的區(qū)別
| 特性 | volatile | synchronized |
|---|---|---|
| 保證可見性 | ? | ? |
| 保證原子性 | ?(除簡單讀寫) | ? |
| 保證有序性 | ?(禁止重排序) | ?(隱式包含) |
| 性能開銷 | 低(無鎖) | 較高(涉及 Monitor 鎖) |
| 適用范圍 | 僅變量 | 方法/代碼塊/變量 |
volatile是 synchronized 的輕量替代方案,但功能更弱。
五、補(bǔ)充:long 和 double 的特殊性
Java 規(guī)范規(guī)定:
- 對 非 volatile 的 long/double,讀寫可能被拆分為兩個 32 位操作(非原子)
- 對 volatile 的 long/double,JVM 保證讀寫是原子的(64 位一次性操作)
總結(jié)
volatile 的核心原理是:
- 通過內(nèi)存屏障實(shí)現(xiàn)變量的可見性;
- 禁止編譯器和處理器重排序,保證有序性;
- 不提供原子性,不能用于復(fù)合操作。
? 適用條件:
當(dāng)一個變量被多個線程訪問,且滿足:
- 寫操作不依賴當(dāng)前值(如
flag = true)- 或者只有一個線程寫,其他線程只讀
否則,請使用 synchronized、ReentrantLock 或 java.util.concurrent.atomic 包中的原子類。
如需進(jìn)一步了解內(nèi)存屏障或 JMM 模型,也可以繼續(xù)提問!
到此這篇關(guān)于java中volatile關(guān)鍵字解釋說明的文章就介紹到這了,更多相關(guān)java volatile關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis的xml中字符串類型判空與非字符串類型判空處理方式(最新整理)
本文給大家介紹MyBatis的xml中字符串類型判空與非字符串類型判空處理方式,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2025-08-08
SpringBoot項(xiàng)目中如何實(shí)現(xiàn)MySQL讀寫分離詳解
在高并發(fā)下需要對應(yīng)用進(jìn)行讀寫分離,配置多數(shù)據(jù)源,即寫操作走主庫,讀操作則走從庫,主從數(shù)據(jù)庫負(fù)責(zé)各自的讀和寫,緩解了鎖的爭用,提高了讀取性能,這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目中如何實(shí)現(xiàn)MySQL讀寫分離的相關(guān)資料,需要的朋友可以參考下2022-07-07
RestTemplate對HttpClient的適配源碼解讀
這篇文章主要為大家介紹了RestTemplate對HttpClient的適配源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
深入了解Springboot核心知識點(diǎn)之?dāng)?shù)據(jù)訪問配置
這篇文章主要為大家介紹了Springboot核心知識點(diǎn)中的數(shù)據(jù)訪問配置,文中的示例代碼講解詳細(xì),對我們了解SpringBoot有一定幫助,快跟隨小編一起學(xué)習(xí)一下吧2021-12-12
RocketMQ中消費(fèi)者的消費(fèi)進(jìn)度管理
這篇文章主要介紹了RocketMQ中消費(fèi)者的消費(fèi)進(jìn)度管理,業(yè)務(wù)實(shí)現(xiàn)消費(fèi)回調(diào)的時候,當(dāng)且僅當(dāng)此回調(diào)函數(shù)返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS ,RocketMQ才會認(rèn)為這批消息(默認(rèn)是1條)是消費(fèi)完成的,需要的朋友可以參考下2023-10-10

