介紹
使用 volatile 修飾的變量是線程共享的全局變量,是輕量級(jí)鎖的一種表現(xiàn)形式涮帘,因?yàn)椴恍枰€程上線文切換和調(diào)度這些操作拼苍,效率杠杠的,但是不能保證原子性调缨,并發(fā)場(chǎng)景下要小心使用疮鲫,比如:多個(gè)線程同時(shí)執(zhí)行 i++ 是有問(wèn)題的。
volatile 的 Demo 代碼:
/**
* 單例模式(懶漢式)
* @date:2020 年 7 月 14 日 上午 9:48:24
*/
public class Singleton {
public static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { //代碼 1
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Singleton 對(duì)象是使用 volatile 修飾弦叶,所有線程都可見(jiàn)此對(duì)象俊犯,即有可能被多個(gè)線程同時(shí)訪問(wèn)此對(duì)象,比如有 A 和 B 兩條線程同時(shí)進(jìn)入代碼 1伤哺,如果 B 線程獲取鎖進(jìn)行對(duì)象初始化燕侠,A 線程自旋等待拿鎖,B 線程完成初始化對(duì)象后釋放鎖立莉,然后 A 線程獲取鎖后判斷對(duì)象是否為 null绢彤,為了避免再次初始化對(duì)象節(jié)約了系統(tǒng)開(kāi)銷,所以此處必須使用雙重校驗(yàn) null桃序。
特性及原理
可見(jiàn)性
任意一個(gè)線程修改了 volatile 修飾的變量杖虾,其他線程可以馬上識(shí)別到最新值。實(shí)現(xiàn)可見(jiàn)性的原理如下媒熊。
步驟 1:修改本地內(nèi)存,強(qiáng)制刷回主內(nèi)存坟比。
[步驟 2:強(qiáng)制讓其他線程的工作內(nèi)存失效過(guò)期芦鳍。
步驟 3:其他線程重新從主內(nèi)存加載最新值。
單個(gè)讀/寫(xiě)具有原子性
單個(gè) volatile 變量的讀/寫(xiě)(比如 vl=l)具有原子性葛账,復(fù)合操作(比如 i++)不具有原子性柠衅,Demo 代碼如下:
public class VolatileFeaturesA {
private volatile long vol = 0L;
/**
* 單個(gè)讀具有原子性
* @date:2020 年 7 月 14 日 下午 5:02:38
*/
public long get() {
return vol;
}
/**
* 單個(gè)寫(xiě)具有原子性
* @date:2020 年 7 月 14 日 下午 5:01:49
*/
public void set(long l) {
vol = l;
}
/**
* 復(fù)合(多個(gè))讀和寫(xiě)不具有原子性
* @date:2020 年 7 月 14 日 下午 5:02:24
*/
public void getAndAdd() {
vol++;
}
}
互斥性
同一時(shí)刻只允許一個(gè)線程操作 volatile 變量,volatile 修飾的變量在不加鎖的場(chǎng)景下也能實(shí)現(xiàn)有鎖的效果籍琳,類似于互斥鎖菲宴。上面的 VolatileFeaturesA.java 和下面的 VolatileFeaturesB.java 兩個(gè)類實(shí)現(xiàn)的功能是一樣的(除了 getAndAdd 方法)贷祈。
public class VolatileFeaturesB {
long vol = 0L;
/**
* 普通寫(xiě)操作
* @date:2020 年 7 月 14 日 下午 8:18:34
* @param l
*/
public synchronized void set(long l) {
vol = l;
}
/**
* 加 1 操作
* @author songjinzhou
* @date:2020 年 7 月 14 日 下午 8:28:25
*/
public void getAndAdd() {
long temp = get();
temp += 1L;
set(temp);
}
/**
* 普通讀操作
* @date:2020 年 7 月 14 日 下午 8:33:00
* @return
*/
public synchronized long get() {
return vol;
}
}
部分有序性
JVM 是使用內(nèi)存屏障來(lái)禁止指令重排,從而達(dá)到部分有序性效果喝峦,看看下面的 Demo 代碼分析自然明白為什么只是部分有序:
//a势誊、b 是普通變量,flag 是 volatile 變量
int a = 1; //代碼 1
int b = 2; //代碼 2
boolean flag = true; //代碼 3
int a = 3; //代碼 4
int b = 4; //代碼 5
PS:因?yàn)?flag 變量是使用 volatile 修飾谣蠢,則在進(jìn)行指令重排序時(shí)粟耻,不會(huì)把代碼 3 放到代碼 1 和代碼 2 前面,也不會(huì)把代碼 3 放到代碼 4 或者代碼 5 后面眉踱。但是指令重排時(shí)代碼 1 和代碼 2 順序挤忙、代碼 4 和代碼 5 的順序不在禁止重排范圍內(nèi),比如:代碼 2 可能會(huì)被移到代碼 1 之前谈喳。
內(nèi)存屏障類型分為四類册烈。
1. LoadLoadBarriers
指令示例:LoadA —> Loadload —> LoadB
此屏障可以保證 LoadB 和后續(xù)讀指令都可以讀到 LoadA 指令加載的數(shù)據(jù),即讀操作 LoadA 肯定比 LoadB 先執(zhí)行婿禽。
2. StoreStoreBarriers
指令示例:StoreA —> StoreStore —> StoreB
此屏障可以保證 StoreB 和后續(xù)寫(xiě)指令可以操作 StoreA 指令執(zhí)行后的數(shù)據(jù)赏僧,即寫(xiě)操作 StoreA 肯定比 StoreB 先執(zhí)行。
3. LoadStoreBarriers
指令示例: LoadA —> LoadStore —> StoreB
此屏障可以保證 StoreB 和后續(xù)寫(xiě)指令可以讀到 LoadA 指令加載的數(shù)據(jù)谈宛,即讀操作 LoadA 肯定比寫(xiě)操作 StoreB 先執(zhí)行次哈。
4. StoreLoadBarriers
指令示例:StoreA —> StoreLoad —> LoadB
此屏障可以保證 LoadB 和后續(xù)讀指令都可以讀到 StoreA 指令執(zhí)行后的數(shù)據(jù),即寫(xiě)操作 StoreA 肯定比讀操作 LoadB 先執(zhí)行吆录。
實(shí)現(xiàn)有序性的原理:
如果屬性使用了 volatile 修飾窑滞,在編譯的時(shí)候會(huì)在該屬性的前或后插入上面介紹的 4 類內(nèi)存屏障來(lái)禁止指令重排,比如:
- 在 volatile 寫(xiě)操作的前面插入 StoreStoreBarriers 保證 volatile 寫(xiě)操作之前的普通讀寫(xiě)操作執(zhí)行完畢后再執(zhí)行 volatile 寫(xiě)操作恢筝。
- 在 volatile 寫(xiě)操作的后面插入 StoreLoadBarriers 保證 volatile 寫(xiě)操作后的數(shù)據(jù)刷新到主內(nèi)存哀卫,保證之后的 volatile 讀寫(xiě)操作能使用最新數(shù)據(jù)(主內(nèi)存)。
- 在 volatile 讀操作的后面插入 LoadLoadBarriers 和 LoadStoreBarriers 保證 volatile 讀寫(xiě)操作之后的普通讀寫(xiě)操作先把線程本地的變量置為無(wú)效撬槽,再把主內(nèi)存的共享變量更新到本地內(nèi)存此改,之后都使用本地內(nèi)存變量。
volatile 讀操作內(nèi)存屏障:
volatile 寫(xiě)操作內(nèi)存屏障:
3.使用場(chǎng)景
狀態(tài)標(biāo)志侄柔,比如布爾類型狀態(tài)標(biāo)志共啃,作為完成某個(gè)重要事件的標(biāo)識(shí),此標(biāo)識(shí)不能依賴其他任何變量暂题,Demo 代碼如下:
public class Flag {
//任務(wù)是否完成標(biāo)志移剪,true:已完成,false:未完成
volatile boolean finishFlag;
public void finish() {
finishFlag = true;
}
public void doTask() {
while (!finishFlag) {
//keep do task
}
}
}
一次性安全發(fā)布薪者,比如:著名的 double-checked-locking纵苛,demo 代碼上面已貼出。
開(kāi)銷較低的讀,比如:計(jì)算器攻人,Demo 代碼如下取试。
/**
* 計(jì)數(shù)器
*/
public class Counter {
private volatile int value;
//讀操作無(wú)需加鎖,減少同步開(kāi)銷提交性能怀吻,使用 volatile 修飾保證讀操作的可見(jiàn)性瞬浓,每次都可以讀到最新值
public int getValue() {
return value;
}
//寫(xiě)操作使用 synchronized 加鎖,保證原子性
public synchronized int increment() {
return value++;
}
}
最后
覺(jué)得不錯(cuò)的小伙伴記得轉(zhuǎn)發(fā)關(guān)注哦烙博,后續(xù)會(huì)持續(xù)更新精選技術(shù)文章瑟蜈!