小編帶您Volatile的詳解-創(chuàng)新互聯(lián)

volatile關(guān)鍵字修飾的共享變量主要有兩個(gè)特點(diǎn):1.保證了不同線程訪問的內(nèi)存可見性 2.禁止重排序
在說內(nèi)存可見性和有序性之前,我們有必要看一下Java的內(nèi)存模型(注意和JVM內(nèi)存模型的區(qū)分)
為什么要有java內(nèi)存模型?
首先我們知道內(nèi)存訪問和CPU指令在執(zhí)行速度上相差非常大,完全不是一個(gè)數(shù)量級(jí),為了使得java在各個(gè)平臺(tái)上運(yùn)行的差距減少,哪些搞處理器的大佬就在CPU上加了各種高速緩存,來減少內(nèi)存操作和CPU指令的執(zhí)行速度差距。而Java在java層面又進(jìn)行了一波抽象,java內(nèi)存模型將內(nèi)存分為工作內(nèi)存和主存,每個(gè)線程從主存load數(shù)據(jù)到工作內(nèi)存,將load的數(shù)據(jù)賦值給工作內(nèi)存上的變量,然后該工作內(nèi)存對應(yīng)的線程進(jìn)行處理,處理結(jié)果在賦值給其工作內(nèi)存,然后再將數(shù)據(jù)賦值給主存中的變量(這時(shí)候需要有一張圖)。
小編帶您Volatile的詳解
小編帶您Volatile的詳解
使用工作內(nèi)存和主存雖然加快了處理速度,但是也帶來了一些問題,比如下面這個(gè)例子
1 int i = 1;
2 i = i+1;
當(dāng)在單線程情況下,i最后的值一定是2;但是在兩個(gè)線程情況下一定是3嗎?那就未必了。當(dāng)線程A讀取i的值為1,load到其工作內(nèi)存,這時(shí)CPU切換至線程B,線程B讀取i的值也是1,然后對加1然后save到主存,這時(shí)線程A也對i進(jìn)行加1,也save回主存,但最終i的值為2。如果寫操作比較慢,你讀到的值還有可能是1,這就是緩存不一致的問題。JMM就是圍繞著原子性,內(nèi)存可見性,有序性這三個(gè)特征建立的。通過解決這個(gè)三個(gè)特征來解決緩存不一致的問題。而volatile主要針對于內(nèi)存可見性和有序性。
原子性
原子性是指一個(gè)操作要么成功,那么失敗,沒有中間狀態(tài),比如i=1,直接讀取i的值,這肯定是原子操作;但是i++,看似好像是,其實(shí)需要先讀取i的值,然后+1,最后在賦值給i,需要三個(gè)步驟,這就不是原子性操作。在JDK1.5引入了boolean、long、int對應(yīng)的原子性類AtomicBoolean、AtomicLong、AtomicInteger,他們可以提供原子性操作。
內(nèi)存可見性
具有內(nèi)存可見性的變量在被線程修改以后,會(huì)立刻刷新到主存并使其他線程的緩存行上的數(shù)據(jù)失效。
volatile修飾的變量具有內(nèi)存可見性,主要表現(xiàn)為:當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)將該線程對應(yīng)的工作內(nèi)存中的共享變量立即刷新到主存;當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對應(yīng)的工作內(nèi)存中的值置為無效,然后從主存中進(jìn)行讀取,但是如果沒有線程對該共享變量進(jìn)行修改,則不會(huì)觸發(fā)該操作。
有序性
JMM是允許處理器和編譯器對指令進(jìn)行重排序的,但規(guī)定了as-if-serial,即無論怎么重排序,最終結(jié)果都是一樣的。比如下面這段代碼:
1 int weight = 10; //A
2 int high = 5; //B
3 int area = high weight high; //C
這段代碼中可以按照A-->B-->C執(zhí)行,也可以按照B-->A-->C執(zhí)行,因?yàn)锳和B是相互獨(dú)立的,而C依賴于A、B,所以C不能排到A或B的前面。JMM保證了單線程的重排序,但是在多線程中就容易出現(xiàn)問題。比如下面這種情況
小編帶您Volatile的詳解
小編帶您Volatile的詳解
1 boolean flag = false;
2 int a = 0;
4 public void write(){
5 int a = 2; //1
6 flag = true; //2
7 }
8 public void multiply(){
9 if(flag){ //3
10 int ret = a * a ; //4
11 }
12 }
小編帶您Volatile的詳解
小編帶您Volatile的詳解
如果有兩個(gè)線程執(zhí)行上面的代碼,線程1先執(zhí)行write方法,隨后線程2執(zhí)行multiply方法。最后結(jié)果一定是4嗎,不一定。
小編帶您Volatile的詳解
小編帶您Volatile的詳解
如圖,JMM對1和2進(jìn)行了重排序,先將flag設(shè)置為true,這是線程2執(zhí)行,由于a還沒有賦值,所以最后ret的值為0;
如果使用volatile關(guān)鍵字修飾flag,禁止重排序,可以保證程序的有序性,也可以使用synchronized或者lock這種重量級(jí)鎖來保證有序性,但性能會(huì)下降。
另外,JMM具備一些先天的有序性,即不需要通過任何手段就可以保證的有序性,通常稱為happens-before原則。<<JSR-133:Java Memory Model and Thread Specification>>定義了如下happens-before規(guī)則:
第1條規(guī)則程序順序規(guī)則是說在一個(gè)線程里,所有的操作都是按順序的,但是在JMM里其實(shí)只要執(zhí)行結(jié)果一樣,是允許重排序的,這邊的happens-before強(qiáng)調(diào)的重點(diǎn)也是單線程執(zhí)行結(jié)果的正確性,但是無法保證多線程也是如此。
第2條規(guī)則監(jiān)視器規(guī)則其實(shí)也好理解,就是在加鎖之前,確定這個(gè)鎖之前已經(jīng)被釋放了,才能繼續(xù)加鎖。
第3條規(guī)則,就適用到所討論的volatile,如果一個(gè)線程先去寫一個(gè)變量,另外一個(gè)線程再去讀,那么寫入操作一定在讀操作之前。
第4條規(guī)則,就是happens-before的傳遞性。
需要注意的是,被volatile修飾的共享變量只滿足內(nèi)存可見性和禁止重排序,并不能保證原子性。比如volatile i++。
小編帶您Volatile的詳解
小編帶您Volatile的詳解
1 public class Test {
2 public volatile int inc = 0;
4 public void increase() {
5 inc++;
6 }
8 public static void main(String[] args) {
9 final Test test = new Test();
10 for(int i=0;i<10;i++){
11 new Thread(){
12 public void run() {
13 for(int j=0;j<1000;j++)
14 test.increase();
15 };
16 }.start();
17 }
18
19 while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
20 Thread.yield();
21 System.out.println(test.inc);
22 }
小編帶您Volatile的詳解
小編帶您Volatile的詳解
按道理來說結(jié)果是10000,但是運(yùn)行下很可能是個(gè)小于10000的值。有人可能會(huì)說volatile不是保證了可見性啊,一個(gè)線程對inc的修改,另外一個(gè)線程應(yīng)該立刻看到啊!可是這里的操作inc++是個(gè)復(fù)合操作啊,包括讀取inc的值,對其自增,然后再寫回主存。
假設(shè)線程A,讀取了inc的值為10,這時(shí)候被阻塞了,因?yàn)闆]有對變量進(jìn)行修改,觸發(fā)不了volatile規(guī)則。
線程B此時(shí)也讀讀inc的值,主存里inc的值依舊為10,做自增,然后立刻就被寫回主存了,為11。
此時(shí)又輪到線程A執(zhí)行,由于工作內(nèi)存里保存的是10,所以繼續(xù)做自增,再寫回主存,11又被寫了一遍。所以雖然兩個(gè)線程執(zhí)行了兩次increase(),結(jié)果卻只加了一次。
有人說,volatile不是會(huì)使緩存行無效的嗎?但是這里線程A讀取到線程B也進(jìn)行操作之前,并沒有修改inc值,所以線程B讀取的時(shí)候,還是讀的10。
又有人說,線程B將11寫回主存,不會(huì)把線程A的緩存行設(shè)為無效嗎?但是線程A的讀取操作已經(jīng)做過了啊,只有在做讀取操作時(shí),發(fā)現(xiàn)自己緩存行無效,才會(huì)去讀主存的值,所以這里線程A只能繼續(xù)做自增了。
綜上所述,在這種復(fù)合操作的情景下,原子性的功能是維持不了了。但是volatile在上面那種設(shè)置flag值的例子里,由于對flag的讀/寫操作都是單步的,所以還是能保證原子性的。
要想保證原子性,只能借助于synchronized,Lock以及并發(fā)包下的atomic的原子操作類了,即對基本數(shù)據(jù)類型的 自增(加1操作),自減(減1操作)、以及加法操作(加一個(gè)數(shù)),減法操作(減一個(gè)數(shù))進(jìn)行了封裝,保證這些操作是原子性操作。
volatile底層原理
如果將使用volatile修飾的代碼和未使用volatile修飾的代碼都編譯成匯編語言,會(huì)發(fā)現(xiàn),使用volatile修飾的代碼會(huì)多出一個(gè)lock前綴指令。
lock前綴指令相當(dāng)于一個(gè)內(nèi)存屏障,內(nèi)存屏障的作用有以下三點(diǎn):
①重排序時(shí),不能把內(nèi)存屏障后面的指令排序到內(nèi)存屏障前
②使得本CPU的cache寫入內(nèi)存
③寫入動(dòng)作會(huì)引起其他CPU緩存或內(nèi)核的數(shù)據(jù)無效,相當(dāng)于修改對其他線程可見。
volatile的應(yīng)用場景
因?yàn)関olatile對復(fù)合操作無效,所以volatile修飾像上面例子中的flag這樣的只會(huì)發(fā)生讀/寫的標(biāo)記型字段。
在單利模式中,volatile還可以修飾成員變量,防止初始化時(shí)的指令重排序。
小編帶您Volatile的詳解
小編帶您Volatile的詳解
1 class Singleton{
2 private volatile static Singleton instance= null;
4 private Singleton(){
6 }
8 public static Singleton getInstance(){
9 if(instance==null){
10 synchronized(Singleton.class){
11 if(instance==null){
12 instance = new Singleton();
13 }
14 }
15 }
16 return instance;
17 }
18 } 小編每天會(huì)定期更新論文及視頻,希望大家多多關(guān)注與支持 每天晚上20:00會(huì)在騰訊課堂上分享免費(fèi)往期干貨QQ:561487941

創(chuàng)新互聯(lián)公司長期為成百上千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為山西企業(yè)提供專業(yè)的成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì),山西網(wǎng)站改版等技術(shù)服務(wù)。擁有十年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

分享題目:小編帶您Volatile的詳解-創(chuàng)新互聯(lián)
文章路徑:http://muchs.cn/article26/cesocg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)、建站公司、微信小程序Google、網(wǎng)站維護(hù)、網(wǎng)站導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)