Java中的volatile關(guān)鍵字經(jīng)典應(yīng)用場(chǎng)景和常見(jiàn)問(wèn)題
一、概述
volatile 是 Java 中用于修飾變量的關(guān)鍵字,它提供了一種輕量級(jí)的線程間通信機(jī)制。與 synchronized 相比,volatile 不會(huì)引起線程上下文切換和調(diào)度,因此性能開銷更小。然而,其同步能力有限,使用不當(dāng)容易產(chǎn)生線程安全問(wèn)題。
二、核心特性
1.可見(jiàn)性保證(Visibility)
- 當(dāng)某個(gè)線程修改
volatile變量的值時(shí),該值會(huì)立即被強(qiáng)制刷新到主內(nèi)存 - 其他線程讀取該變量時(shí),會(huì)從主內(nèi)存重新加載最新值,而非使用本地線程緩存
- 解決了多線程環(huán)境下因 CPU 緩存導(dǎo)致的數(shù)據(jù)不一致問(wèn)題
2.有序性保證(Ordering)
- 禁止編譯器和處理器對(duì)
volatile變量的讀寫操作進(jìn)行指令重排序 - 確保:
volatile寫操作之前的任何讀寫操作不會(huì)被重排序到寫之后volatile讀操作之后的任何讀寫操作不會(huì)被重排序到讀之前
- 建立 happens-before 關(guān)系,確保多線程間的操作順序可見(jiàn)性
3.不保證原子性(Non-Atomicity)
volatile無(wú)法保證復(fù)合操作的原子性(如i++)- 復(fù)合操作由多個(gè)步驟組成,中間可能被其他線程中斷
三、內(nèi)存語(yǔ)義
volatile 寫操作的內(nèi)存屏障
[普通寫/讀操作] → [StoreStore屏障] → [volatile寫] → [StoreLoad屏障]
volatile 讀操作的內(nèi)存屏障
[volatile讀] → [LoadLoad屏障] → [LoadStore屏障] → [普通寫/讀操作]
內(nèi)存屏障的作用:
- StoreStore屏障:確保 volatile 寫之前的普通寫操作已刷新到主內(nèi)存
- StoreLoad屏障:確保 volatile 寫完成后,后續(xù)的讀操作能看到所有之前的寫入
- LoadLoad屏障:確保 volatile 讀之后的操作不會(huì)被重排序到讀之前
- LoadStore屏障:確保 volatile 讀之后的寫操作不會(huì)被重排序到讀之前
四、經(jīng)典應(yīng)用場(chǎng)景
1.狀態(tài)標(biāo)志(最常用)
public class ShutdownHandler {
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 執(zhí)行任務(wù)
}
}
}
2.雙重檢查鎖定單例模式(DCL)
public class Singleton {
// 必須使用 volatile 防止指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查:避免不必要的同步
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查:確保單例
// 創(chuàng)建對(duì)象分為三步(無(wú)volatile可能重排序):
// 1. 分配內(nèi)存空間
// 2. 初始化對(duì)象
// 3. 將引用指向內(nèi)存地址
// volatile 確保 2 在 3 之前完成
instance = new Singleton();
}
}
}
return instance;
}
}
3.一次性安全發(fā)布(One-Time Safe Publication)
public class Resource {
private volatile Resource resource;
public Resource getResource() {
if (resource == null) {
synchronized (this) {
if (resource == null) {
resource = new Resource();
}
}
}
return resource;
}
}
4.獨(dú)立觀察(Independent Observation)
public class SensorReader {
private volatile double currentTemperature;
// 一個(gè)線程定期更新溫度
public void updateTemperature(double temp) {
currentTemperature = temp;
}
// 多個(gè)線程同時(shí)讀取最新的溫度值
public double getTemperature() {
return currentTemperature;
}
}
五、volatile與synchronized詳細(xì)對(duì)比
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | 僅保證單個(gè)讀/寫操作的原子性 不保證復(fù)合操作(如 i++)的原子性 | 保證整個(gè)代碼塊/方法的原子性 |
| 可見(jiàn)性 | 保證變量對(duì)所有線程立即可見(jiàn) | 保證變量對(duì)所有線程可見(jiàn) |
| 有序性 | 禁止指令重排序 (通過(guò)內(nèi)存屏障實(shí)現(xiàn)) | 保證有序性 (但允許同步塊內(nèi)重排序) |
| 阻塞性 | 非阻塞機(jī)制 線程不會(huì)掛起 | 阻塞機(jī)制 獲取不到鎖的線程會(huì)掛起等待 |
| 性能 | 輕量級(jí),性能開銷小 (僅內(nèi)存屏障開銷) | 重量級(jí),性能開銷較大 (涉及鎖競(jìng)爭(zhēng)、上下文切換) |
| 作用范圍 | 變量級(jí)別 | 代碼塊或方法級(jí)別 |
| 適用場(chǎng)景 | 狀態(tài)標(biāo)志、一次性發(fā)布 | 復(fù)雜同步邏輯、需要原子性的復(fù)合操作 |
六、常見(jiàn)誤區(qū)與注意事項(xiàng)
錯(cuò)誤示例:誤以為 volatile 能保證原子性
public class Counter {
private volatile int count = 0;
// 線程不安全!count++ 不是原子操作
public void increment() {
count++; // 實(shí)際包含:讀 → 加1 → 寫 三個(gè)步驟
}
}
正確解決方案
// 方案1:使用 synchronized(適合復(fù)雜同步)
public class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
// 方案2:使用原子類(推薦,性能更好)
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS操作,保證原子性
}
public int getCount() {
return count.get();
}
}
// 方案3:使用 volatile + CAS(高級(jí)用法)
public class VolatileCASCounter {
private volatile int count = 0;
private static final sun.misc.Unsafe UNSAFE = // 獲取Unsafe實(shí)例
public void increment() {
int current;
do {
current = count;
} while (!UNSAFE.compareAndSwapInt(this, OFFSET, current, current + 1));
}
}
使用建議總結(jié)
- 優(yōu)先考慮不可變對(duì)象:避免使用
volatile,設(shè)計(jì)為不可變對(duì)象 - 一個(gè)寫線程,多個(gè)讀線程:典型的
volatile適用場(chǎng)景 - 變量不參與不變性約束:
volatile變量不應(yīng)依賴于其他變量,也不應(yīng)被其他變量依賴 - 替代方案優(yōu)先:考慮使用
java.util.concurrent.atomic包下的原子類 - 謹(jǐn)慎使用:只在明確理解其語(yǔ)義的場(chǎng)景下使用
七、與 JMM(Java內(nèi)存模型)的關(guān)系
volatile 變量的讀寫操作建立了 happens-before 關(guān)系:
- 對(duì)
volatile變量的寫操作 happens-before 于后續(xù)對(duì)該變量的讀操作 - 這與
synchronized的釋放鎖 happens-before 于獲取鎖的語(yǔ)義類似
八、性能考量
性能測(cè)試對(duì)比
// volatile 變量訪問(wèn) vs 普通變量訪問(wèn) volatile int vCounter = 0; int counter = 0; // volatile 訪問(wèn)有約10-20%的性能損耗 // 但相比 synchronized 的數(shù)千倍性能損耗可忽略不計(jì)
最佳實(shí)踐
- 避免過(guò)度使用:不必要的
volatile修飾會(huì)增加內(nèi)存屏障開銷 - 結(jié)合使用:
volatile適合與final結(jié)合,用于安全發(fā)布 - 替代方案:考慮使用
ThreadLocal避免共享變量
九、擴(kuò)展知識(shí):volatile 在 JSR-133 中的增強(qiáng)
在 Java 5 之前,volatile 只保證可見(jiàn)性,不保證有序性。JSR-133(Java內(nèi)存模型修訂)強(qiáng)化了 volatile 的語(yǔ)義,通過(guò)內(nèi)存屏障同時(shí)保證了可見(jiàn)性和有序性。
十、總結(jié)
volatile 是 Java 并發(fā)編程中的重要工具,但理解其局限性至關(guān)重要:
適合使用 volatile 的場(chǎng)景:
- 狀態(tài)標(biāo)志位(一個(gè)線程寫,多個(gè)線程讀)
- 一次性安全發(fā)布(如單例模式的DCL實(shí)現(xiàn))
- 獨(dú)立觀察(如傳感器數(shù)據(jù)讀?。?/li>
不適合使用 volatile 的場(chǎng)景:
- 需要原子性的復(fù)合操作
- 變量參與不變性約束
- 依賴其他變量的計(jì)算
黃金法則: 當(dāng)您不確定是否需要使用 volatile 時(shí),優(yōu)先考慮使用更高層次的并發(fā)工具(如 java.util.concurrent 包中的類),這些工具通常封裝了更安全、更高效的并發(fā)實(shí)現(xiàn)。
到此這篇關(guān)于Java中volatile關(guān)鍵字經(jīng)典應(yīng)用場(chǎng)景和常見(jiàn)問(wèn)題的文章就介紹到這了,更多相關(guān)Java中volatile關(guān)鍵字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入解析Java中volatile關(guān)鍵字的作用
- Java中volatile關(guān)鍵字的作用與用法詳解
- Java中Volatile關(guān)鍵字詳解及代碼示例
- java volatile關(guān)鍵字作用及使用場(chǎng)景詳解
- Java中volatile關(guān)鍵字實(shí)現(xiàn)原理
- java volatile關(guān)鍵字使用方法及注意事項(xiàng)
- 談?wù)凧ava中Volatile關(guān)鍵字的理解
- 詳解Java面試官最愛(ài)問(wèn)的volatile關(guān)鍵字
- 詳解Java線程編程中的volatile關(guān)鍵字的作用
- Java里volatile關(guān)鍵字是什么意思
相關(guān)文章
SpringBoot中的@ConfigurationProperties注解解析
這篇文章主要介紹了SpringBoot中的@ConfigurationProperties注解解析,Spring源碼中大量使用了ConfigurationProperties注解,通過(guò)與其他注解配合使用,能夠?qū)崿F(xiàn)Bean的按需配置,該注解可以放在類上,也可以放在方法上,需要的朋友可以參考下2023-11-11
SpringBoot使用Validation實(shí)現(xiàn)接口校驗(yàn)的超全使用指南
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用Validation實(shí)現(xiàn)接口校驗(yàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-03-03
idea項(xiàng)目的左側(cè)目錄沒(méi)了如何設(shè)置
這篇文章主要介紹了idea項(xiàng)目的左側(cè)目錄沒(méi)了如何設(shè)置的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02

