Volatile的精妙應(yīng)用和原理解析
volatile 是并發(fā)編程中的重要關(guān)鍵字,它的名氣甚至是可以與 synchronized、ReentrantLock 等齊名,也是屬于并發(fā)編程五杰之一。
需要注意的是 volatile 并不能保證原子性,因此使用 volatile 并沒有辦法保證線程安全。
并發(fā)編程五杰:
PS:“并發(fā)編程五杰”是我個(gè)人起的名字,大家也不用太當(dāng)真。
1.什么是 volatile?
volatile 是 Java 中的一個(gè)關(guān)鍵字,用于修飾變量,它的主要作用是保證變量的可見性和禁止指令重排序。
- 可見性:是指當(dāng)一個(gè)線程修改了一個(gè)被 volatile 修飾的變量時(shí),其他線程能夠立即看到這個(gè)修改。
- 禁止指令重排序:則是確保對(duì) volatile 變量的讀寫操作不會(huì)被編譯器或處理器隨意重新排序,從而保證了程序執(zhí)行的順序符合我們的預(yù)期。
2.volatile 工作原理
為了實(shí)現(xiàn)可見性,Java 內(nèi)存模型(JMM)會(huì)在對(duì) volatile 變量進(jìn)行寫操作時(shí),強(qiáng)制將工作內(nèi)存中的值刷新到主內(nèi)存,并在讀取時(shí)強(qiáng)制從主內(nèi)存中重新獲取最新的值。
而禁止指令重排序是通過(guò)在編譯器和處理器層面添加特定的內(nèi)存屏障指令來(lái)實(shí)現(xiàn)的。
具體來(lái)說(shuō)。
(1)可見性實(shí)現(xiàn)原理
可見性:在計(jì)算機(jī)編程特別是多線程編程中,“可見性”指的是一個(gè)線程對(duì)共享變量的修改,對(duì)于其他線程是否能夠及時(shí)地、準(zhǔn)確地“可見”,即其他線程是否能夠及時(shí)感知到這個(gè)修改并獲取到最新的值。
例如,在一個(gè)多線程環(huán)境中,如果線程 A 修改了一個(gè)共享變量的值,而線程 B 無(wú)法立即看到這個(gè)修改,那么就存在可見性問題。
多線程操作共享變量流程如下:
volatile 是通過(guò)內(nèi)存屏障(Memory Barrier)來(lái)確保可見性。
- 寫屏障(Store Barrier):在 volatile 變量的寫操作之后插入寫屏障,確保所有之前的寫操作都同步到主內(nèi)存中,從而使得其他線程在讀取該變量時(shí)能夠獲取到最新的值。
- 讀屏障(Load Barrier):在 volatile 變量的讀操作之前插入讀屏障,確保所有之前的寫操作都已完成,從而讀取到的是最新的值。
通過(guò)這種方式,volatile 變量在多線程環(huán)境下的讀寫操作能夠保持較高的可見性,但需要注意的是,volatile 并不保證操作的原子性。
具體來(lái)說(shuō),volatile 內(nèi)存可見性主要通過(guò) lock 前綴指令實(shí)現(xiàn)的,它會(huì)鎖定當(dāng)前內(nèi)存區(qū)域的緩存(緩存行),并且立即將當(dāng)前緩存行數(shù)據(jù)寫入主內(nèi)存(耗時(shí)非常短),回寫主內(nèi)存的時(shí)候會(huì)通知其他線程緩存了該變量的地址失效,從而導(dǎo)致其他線程需要重新去主內(nèi)存中重新讀取數(shù)據(jù)到其工作線程中。
(2)有序性實(shí)現(xiàn)原理
volatile 的有序性是通過(guò)插入內(nèi)存屏障,在內(nèi)存屏障前后禁止重排序優(yōu)化,以此實(shí)現(xiàn)有序性的。
(3)正確理解“內(nèi)存屏障”?
volatile 保證可見性的“內(nèi)存屏障”和保證有序性的“內(nèi)存屏障”有什么區(qū)別呢?
在說(shuō)它們的區(qū)別之前,我們現(xiàn)需要對(duì)“內(nèi)存屏障”有一個(gè)大致的理解。
內(nèi)存屏障,簡(jiǎn)單來(lái)說(shuō),就像是在內(nèi)存操作中的一道“關(guān)卡”或者“柵欄”。
想象一下,計(jì)算機(jī)在執(zhí)行程序的時(shí)候,為了提高效率,可能會(huì)對(duì)指令的執(zhí)行順序進(jìn)行一些調(diào)整。但是在多線程或者多核心的環(huán)境下,這種隨意的調(diào)整可能會(huì)導(dǎo)致一些問題。
內(nèi)存屏障的作用就是阻止這種隨意的調(diào)整,確保特定的內(nèi)存操作按照我們期望的順序執(zhí)行。
所以“內(nèi)存屏障”本身只是一種“技術(shù)”,而這種“技術(shù)”可以實(shí)現(xiàn)很多“業(yè)務(wù)功能”。
這就像 Spring 中的 AOP 一樣,AOP 是一種“技術(shù)”,而這種技術(shù)可以實(shí)現(xiàn)很多業(yè)務(wù)功能。例如,針對(duì)日志處理可以使用 AOP、針對(duì)用戶鑒權(quán)可以使用 AOP 等,而內(nèi)存屏障也是一樣,我們可以使用內(nèi)存屏障實(shí)現(xiàn)可見性的“業(yè)務(wù)功能”,也可以實(shí)現(xiàn)有序性的“業(yè)務(wù)功能”等。
3.volatile 適用場(chǎng)景
volatile 常見場(chǎng)景有以下兩種:
- 狀態(tài)標(biāo)記
- 單例模式中的雙重檢查鎖
具體來(lái)說(shuō)。
(1)狀態(tài)標(biāo)記
例如,在多線程環(huán)境中用于表示某個(gè)任務(wù)是否完成的標(biāo)志變量,具體代碼如下:
volatile boolean isTaskFinished = false;
(2)單例模式中的雙重檢查鎖
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4.volatile 局限性
volatile 并不能保證原子性,也就是并不能保證線程安全。
例如,對(duì)于 i++ 這樣的操作,它不是一個(gè)原子操作,單純使用 volatile 修飾 i 并不能保證線程安全。