Java volatile的幾種使用場景分析
回答
volatile
是一種輕量級(jí)的同步機(jī)制,它能保證共享變量的可見性,同時(shí)禁止重排序保證了操作的有序性,但是它無法保證原子性。所以使用 volatile
必須要滿足這兩個(gè)條件:
- 寫入變量不依賴當(dāng)前值。
- 變量不參與與其他變量的不變性條件。
volatile
比較適合多個(gè)線程讀,一個(gè)線程寫的場合,典型的場景有如下幾個(gè):
- 狀態(tài)標(biāo)志
- 重檢查鎖定的單例模式
- 開銷較低的“讀-寫鎖”策略
詳解
volatile 使用條件
要想正確安全地使用 volatile
,必須要具備這兩個(gè)條件:
- 寫入變量不依賴當(dāng)前值:變量的新值不能依賴于之前的舊值。如果變量的當(dāng)前值與新值之間存在依賴關(guān)系,那么僅使用
volatile
是不夠的,因?yàn)樗荒鼙WC一系列操作的原子性。比如 i++。 - 變量不參與與其他變量的不變性條件:如果一個(gè)變量是與其他變量共同參與不變性條件的一部分,那么簡單地聲明變量為
volatile
是不夠的。
第一個(gè)條件很好理解,第二個(gè)條件這里需要解釋下。
“變量不參與與其他變量的不變性條件”,這里的“不變性條件”指的是一個(gè)或多個(gè)變量在程序執(zhí)行過程中需要保持的條件或關(guān)系,以確保程序的正確性。假設(shè)我們有兩個(gè)變量,它們需要滿足某種關(guān)系(例如,a + b = 99
)。我們需要在多線程環(huán)境下保證這種關(guān)閉在任何時(shí)候都是成立的。如果這個(gè)時(shí)候我們只是將其中一個(gè)變量聲明為 volatile
,雖然確保了這個(gè)變量的更新對(duì)其他線程立即可見,但卻不能保證這兩個(gè)變量作為一個(gè)整體滿足特定的不變性條件。在更新這兩個(gè)變量的過程中,其他線程可能會(huì)看到這些變量處于不一致的狀態(tài)。在這種情況下我們就需要使用鎖或者其他同步機(jī)制來保證這種關(guān)系的整體一致性。
volatile 使用場景
volatile
比較適合多個(gè)線程讀,一個(gè)線程寫的場合。
狀態(tài)標(biāo)志
當(dāng)我們需要用一個(gè)變量來作為狀態(tài)標(biāo)志,控制線程的執(zhí)行流程時(shí),使用 volatile
可以確保當(dāng)一個(gè)線程修改了這個(gè)標(biāo)志時(shí),其他線程能夠立即看到最新的值。
public class TaskRunner implements Runnable { private volatile boolean running = true; // 狀態(tài)標(biāo)志,控制任務(wù)是否繼續(xù)執(zhí)行 public void run() { while (running) { // 檢查狀態(tài)標(biāo)志 // 執(zhí)行任務(wù) doSomething(); } } public void stop() { running = false; // 修改狀態(tài)標(biāo)志,使得線程能夠停止執(zhí)行 } private void doSomething() { // 實(shí)際任務(wù)邏輯 } }
DCL 的單例模式
在實(shí)現(xiàn)單例模式時(shí),為了保證線程安全,通常使用雙重檢查鎖定(Double-Checked Locking)模式。在這種模式中,volatile
用于避免單例實(shí)例的初始化過程中的指令重排序,確保其他線程看到一個(gè)完全初始化的單例對(duì)象,具體來說,就是使用 volatile
防止了Java 對(duì)象在實(shí)例化過程中的指令重排,確保在對(duì)象的構(gòu)造函數(shù)執(zhí)行完畢之前,不會(huì)將 instance
的內(nèi)存分配操作指令重排到構(gòu)造函數(shù)之外。
public class Singleton { // 使用 volatile 保證實(shí)例的可見性和有序性 private static volatile Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { // 第一次檢查,避免不必要的同步 synchronized (Singleton.class) { // 鎖定 if (instance == null) { // 第二次檢查,確保只創(chuàng)建一次實(shí)例 instance = new Singleton(); } } } return instance; } }
開銷較低的“讀-寫鎖”策略
這種策略一般都是允許多個(gè)線程同時(shí)讀取一個(gè)資源,但只允許一個(gè)線程寫入的同步機(jī)制。這種“讀-寫鎖”非常適合讀多寫少的場景,我們可以利用 volatile
+ 鎖的機(jī)制減少公共代碼路徑的開銷。如下:
public class VolatileTest { private volatile int value; //讀,不加鎖,提供效率 public int getValue() { return value; } //寫操作,使用鎖,保證線程安全 public synchronized int increment() { return value++; } }
在 J.U.C 中,有一個(gè)采用“讀-寫鎖”方式的類:ReentrantReadWriteLock
,它包含兩個(gè)鎖:一個(gè)是讀鎖,另一個(gè)是寫鎖。
下面是偽代碼:
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class DataStructure { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Object data = ...; // 被保護(hù)的數(shù)據(jù) public void read() { readWriteLock.readLock().lock(); // 獲取讀鎖 try { // 執(zhí)行讀操作 // 例如,讀取data的內(nèi)容 } finally { readWriteLock.readLock().unlock(); // 釋放讀鎖 } } public void write(Object newData) { readWriteLock.writeLock().lock(); // 獲取寫鎖 try { // 執(zhí)行寫操作 // 例如,修改data的內(nèi)容 } finally { readWriteLock.writeLock().unlock(); // 釋放寫鎖 } } }
- 讀操作 :多個(gè)線程可以同時(shí)持有讀鎖,因此多個(gè)線程可以同時(shí)執(zhí)行
read()
方法。 - 寫操作: 只有一個(gè)線程可以持有寫鎖,并且在持有寫鎖時(shí),其他線程不能讀取或?qū)懭搿?/li>
這種“讀-寫鎖”策略提高了在多線程環(huán)境下對(duì)共享資源的讀取效率,尤其是在讀操作遠(yuǎn)遠(yuǎn)多于寫操作的情況下。但是,它也會(huì)讓我們的程序變更更加復(fù)雜,比如潛在的讀寫鎖沖突、鎖升級(jí)(從讀鎖升級(jí)到寫鎖)等問題。因此,在實(shí)際應(yīng)用中,大明哥推薦直接使用 ReentrantReadWriteLock
即可,無需頭鐵自己造輪子。
以上就是Java volatile的幾種使用場景分析的詳細(xì)內(nèi)容,更多關(guān)于Java volatile使用場景的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot前端傳參date類型后臺(tái)處理的方式
這篇文章主要介紹了springboot前端傳參date類型后臺(tái)處理的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07java.util.Collections類—emptyList()方法的使用
這篇文章主要介紹了java.util.Collections類—emptyList()方法的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11做java這么久了居然還不知道JSON的使用(一文帶你了解)
這篇文章主要介紹了做java這么久了居然還不知道JSON的使用(一文帶你了解),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07springboot如何開啟緩存@EnableCaching(使用redis)
在Spring Boot項(xiàng)目中集成Redis主要包括添加依賴到pom.xml、配置application.yml中的Redis連接參數(shù)、編寫配置類、在啟動(dòng)類上添加@EnableCaching注解以及測試接口的查詢和緩存驗(yàn)證等步驟,首先,需要在pom.xml中添加spring-boot-starter-data-redis依賴2024-11-11詳解Java如何優(yōu)雅的實(shí)現(xiàn)字典翻譯
當(dāng)我們?cè)贘ava應(yīng)用程序中需要對(duì)字典屬性進(jìn)行轉(zhuǎn)換返回給前端時(shí),如何簡單、方便、并且優(yōu)雅的處理是一個(gè)重要問題。在本文中,我們將介紹如何使用Java中的序列化機(jī)制來優(yōu)雅地實(shí)現(xiàn)字典值的翻譯,從而簡化開發(fā)2023-04-04java數(shù)據(jù)結(jié)構(gòu)與算法之希爾排序詳解
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之希爾排序,結(jié)合實(shí)例形式分析了希爾排序的概念、原理、實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-05-05IDEA中application.properties的圖標(biāo)顯示不正常的問題及解決方法
這篇文章主要介紹了IDEA中application.properties的圖標(biāo)顯示不正常的問題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04java.text.DecimalFormat類十進(jìn)制格式化
這篇文章主要為大家詳細(xì)介紹了java.text.DecimalFormat類十進(jìn)制格式化的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03