內(nèi)存模型
在Java內(nèi)存模型中三圆,線(xiàn)程工作在自己的工作內(nèi)存啼止,他會(huì)保留主存的變量拷貝。對(duì)于普通變量奸焙,為了保證執(zhí)行效率瞎暑,在工作內(nèi)存中對(duì)變量的改變并不會(huì)立刻刷新到主存中中。
Volatile關(guān)鍵字
volatile的意思是易變的与帆,不需要被優(yōu)化的了赌。當(dāng)一個(gè)變量被加上了這個(gè)關(guān)鍵字,就表示這個(gè)變量不需要被優(yōu)化玄糟、緩存勿她。對(duì)該變量的修改會(huì)被立刻刷新到主存中,而當(dāng)其他線(xiàn)程讀取該變量時(shí)阵翎,也會(huì)去主存中讀取新值逢并。
使用場(chǎng)景
1.修飾線(xiàn)程中斷標(biāo)志位
volatile boolean isCancel;
當(dāng)我們希望通過(guò)標(biāo)志位去結(jié)束一個(gè)正在運(yùn)行的線(xiàn)程,那么應(yīng)該對(duì)該標(biāo)志修飾volatile郭卫。
2.單例模式
public class Singleton {
private volatile static Singleton sInstance; //保證對(duì)象的可見(jiàn)性
private Singleton() {
}
public static Singleton getsInstance() {
if (sInstance == null) { //減少每次創(chuàng)建對(duì)象雙重鎖的開(kāi)銷(xiāo)
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
問(wèn):為什么這里的volatile關(guān)鍵字是必要的砍聊?
答:因?yàn)閯?chuàng)建對(duì)象不是一個(gè)原子操作。
禁止指令重排
假設(shè)創(chuàng)建一個(gè)對(duì)象需要3個(gè)步驟:
(1)分配內(nèi)存空間贰军。
(2)初始化對(duì)象玻蝌。
(3)將內(nèi)存空間的地址賦值給對(duì)應(yīng)的引用。
但是由于操作系統(tǒng)可以對(duì)指令進(jìn)行重排序词疼,所以上面的過(guò)程也可能會(huì)變成如下過(guò)程:
(1)分配內(nèi)存空間俯树。
(2)將內(nèi)存空間的地址賦值給對(duì)應(yīng)的引用。
(3)初始化對(duì)象
如果采用后面的流程贰盗,可能導(dǎo)致引用不為null许饿,但是對(duì)象還沒(méi)有初始化出來(lái),其他的線(xiàn)程進(jìn)行非空判斷后舵盈,會(huì)引用這個(gè)對(duì)象陋率,導(dǎo)致錯(cuò)誤球化。
volatile的原理和機(jī)制
下面這段話(huà)摘自《深入理解Java虛擬機(jī)》:
“觀察加入volatile關(guān)鍵字和沒(méi)有加入volatile關(guān)鍵字時(shí)所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時(shí)翘贮,會(huì)多出一個(gè)lock前綴指令”
lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄)赊窥,內(nèi)存屏障會(huì)提供3個(gè)功能:
1.它確定指令重新排序時(shí)不會(huì)把其后的指令排在屏障之前爆惧,也不會(huì)把前面的指令排到屏障之后狸页。即執(zhí)行到屏障指令時(shí),其前面的指令已經(jīng)全部執(zhí)行完扯再,且該屏障指令的執(zhí)行結(jié)果對(duì)后面的指令可見(jiàn)芍耘。
2.它會(huì)強(qiáng)制對(duì)緩存的修改操作立刻寫(xiě)入主存
3.如果是讀取操作,它會(huì)導(dǎo)致其他線(xiàn)程中的緩存變量無(wú)效
synchronized關(guān)鍵字
要理解這個(gè)關(guān)鍵字的作用熄阻,就必須明白Java線(xiàn)程安全中的一個(gè)重要概念---原子性斋竞。
在Java中,對(duì)基本數(shù)據(jù)類(lèi)型變量的讀取和賦值操作是原子性的秃殉。
如果要實(shí)現(xiàn)更大范圍的原子性操作坝初,就必須用synchronized和Lock來(lái)實(shí)現(xiàn),他們能夠保證任一時(shí)刻只有一個(gè)線(xiàn)程執(zhí)行該代碼塊钾军,從而保證了原子性鳄袍。
當(dāng)訪問(wèn)臨界資源時(shí),為了避免數(shù)據(jù)不一致吏恭,我們會(huì)采取同步操作拗小,以確保在某一時(shí)刻只有一個(gè)線(xiàn)程在操作資源。
synchronized的用法
對(duì)象鎖
修飾對(duì)象方法或者明確指定某一個(gè)對(duì)象類(lèi)鎖
修飾靜態(tài)方法或者鎖定.class
同一把鎖在某一時(shí)刻只能被一個(gè)線(xiàn)程持有樱哼,當(dāng)另一個(gè)線(xiàn)程嘗試著獲取該鎖的時(shí)候哀九,會(huì)陷入阻塞狀態(tài)。JVM維持了一個(gè)等待該鎖的隊(duì)列搅幅,一旦該鎖被某個(gè)線(xiàn)程釋放了阅束,那么這個(gè)等待隊(duì)列中的線(xiàn)程會(huì)重新進(jìn)入Ready(可運(yùn)行)狀態(tài),等待調(diào)度器的下一次分配茄唐,進(jìn)而去獲取需要的鎖围俘。
無(wú)論synchronized關(guān)鍵字加在方法上還是對(duì)象上,如果它作用的對(duì)象是非靜態(tài)的琢融,則它取得的鎖是對(duì)象界牡;如果synchronized作用的對(duì)象是一個(gè)靜態(tài)方法或一個(gè)類(lèi),則它取得的鎖是類(lèi)漾抬,該類(lèi)所有的對(duì)象用同一把鎖宿亡。