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