java中的volatile關(guān)鍵字是什么?volatile關(guān)鍵字怎么用?

要想更好的理解volatile關(guān)鍵字,我們先來聊聊基于高速緩存的存儲(chǔ)交互

我們提供的服務(wù)有:網(wǎng)站制作、做網(wǎng)站、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、黃浦ssl等。為近1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的黃浦網(wǎng)站制作公司

java中的volatile關(guān)鍵字是什么?volatile關(guān)鍵字怎么用?

我們知道程序中進(jìn)行計(jì)算的變量是存儲(chǔ)在內(nèi)存中的,而處理器的計(jì)算速度和內(nèi)存的讀取速度完全不在一個(gè)量級,區(qū)別猶如蘭博基尼和自行車。

要讓蘭博基尼開一小段就停下來等會(huì)自行車顯然不太合適,所以在處理器和內(nèi)存之間加了一個(gè)高速緩存,高速緩存速度遠(yuǎn)高于內(nèi)存,猶如奔馳,雖然和蘭博基尼還有一定差距,每個(gè)處理器都對應(yīng)一個(gè)高速緩存。

當(dāng)要對一個(gè)變量進(jìn)行計(jì)算的時(shí)候,先從內(nèi)存中將該變量的值讀取到高速緩存中,再去計(jì)算,效率得到明顯提升,這是從硬件的的視角描述的內(nèi)存。

Jvm虛擬機(jī)從另一個(gè)視角定義的內(nèi)存模型規(guī)定所有變量都存儲(chǔ)在主內(nèi)存中,每個(gè)線程有自己的工作內(nèi)存,每個(gè)線程的工作內(nèi)存只能被該線程獨(dú)占,其它線程不能訪問,所有的線程只能通過主內(nèi)存來共享數(shù)據(jù)。

這里的主內(nèi)存可以類比于硬件視角的內(nèi)存,工作內(nèi)存可以類比于硬件視角的高速緩存。

線程執(zhí)行程序的時(shí)候先將主內(nèi)存中的變量復(fù)制到工作內(nèi)存中進(jìn)行計(jì)算,計(jì)算完畢后再將變量同步到主內(nèi)存中。

這么做雖然解決了執(zhí)行效率的問題,但是同時(shí)也帶來了其它問題。

試想一下,線程A從主內(nèi)存中復(fù)制了一個(gè)變量a=3到工作內(nèi)存,并且對變量a進(jìn)行了加一操作,a變成了4,此時(shí)線程B也從主內(nèi)存中復(fù)制該變量到它自己的工作內(nèi)存,它得到的a的值還是3,a的值不一致了。

用專業(yè)術(shù)語來說就是變量的可見性,此時(shí)變量a對于線程來說變得不可見了。

怎么解決這個(gè)問題?

volatile關(guān)鍵字閃亮登場:

當(dāng)一個(gè)變量被定義為volatile之后,它對所有的線程就具有了可見性,也就是說當(dāng)一個(gè)線程修改了該變量的值,所有的其它線程都可以立即知道,可以從兩個(gè)方面來理解這句話:

1.線程對變量進(jìn)行修改之后,要立刻回寫到主內(nèi)存。

2.線程對變量讀取的時(shí)候,要從主內(nèi)存中讀,而不是工作內(nèi)存。

但是這并不意味著使用了volatile關(guān)鍵字的變量具有了線程安全性,舉個(gè)栗子:

public class AddThread implements Runnable {

    private volatile int num=0;

    @Override

    public void run() {

        for (int i=1;i<=10000;i++){

            num=num+1;

            System.out.println(num);

        }

    }

}

 

 

 

public class VolatileThread {

    public static void main(String[] args) {

        Thread[] th = new Thread[20];

        AddThread addTh = new AddThread();

        for(int i=1;i<=20;i++){

            th[i] = new Thread(addTh);

            th[i].start();

        }

    }

}

 

這里我們創(chuàng)建了20個(gè)線程,每個(gè)線程對num進(jìn)行10000次累加。

按理結(jié)果應(yīng)該是打印1,2,3.。。。。。200000 。

但是結(jié)果卻是1,2,3…..x ,x小于200000.

為什么會(huì)是這樣的結(jié)果?

我們仔細(xì)分析一下這行代碼:num=num+1;

雖然只有一行代碼,但是被編譯為字節(jié)碼以后會(huì)對應(yīng)四條指令:

1.Getstatic將num的值從主內(nèi)存取出到線程的工作內(nèi)存

2.Iconst_1和 iadd 將num的值加一

3.Putstatic將結(jié)果同步回主內(nèi)存

在第一步Getstatic將num的值從主內(nèi)存取出到線程的工作內(nèi)存因?yàn)閚um加了Volatile關(guān)鍵字,可以保證它的值是正確的,但是在執(zhí)行第二步的時(shí)候其它的線程有可能已經(jīng)將num的值加大了。在第三步就會(huì)將較小的值同步到內(nèi)存,于是造成了我們看到的結(jié)果。

既然如此,Volatile在什么場合下可以用到呢?

一個(gè)變量,如果有多個(gè)線程只有一個(gè)線程會(huì)去修改這個(gè)變量,其它線程都只是讀取該變量的值就可以使用Volatile關(guān)鍵字,為什么呢?一個(gè)線程修改了該變量,其它線程會(huì)立刻獲取到修改后的值。

因?yàn)閂olatile的特性可以保證這些線程獲取到的都是正確的值,而他們又不會(huì)去修改這個(gè)變量,不會(huì)造成該變量在各個(gè)線程中不一致的情況。當(dāng)然這種場合也可以用synchronized關(guān)鍵字

當(dāng)運(yùn)算結(jié)果并不依賴變量的當(dāng)前值的時(shí)候該變量也可以使用Volatile關(guān)鍵字,上栗子:

public class shutDownThread implements Runnable {

    volatile boolean shutDownRequested;

    public void shutDown(){

        shutDownRequested = true;

    }

    @Override

    public void run() {

        while (!shutDownRequested) {

            System.out.println("work!");

        }

    }

}

 

 

public class Demo01 {

    public static void main(String[] args) throws InterruptedException {

        Thread[] th = new Thread[10];

        shutDownThread t = new shutDownThread();

        for(int i=0;i<=9;i++){

            th[i] = new Thread(t);

            th[i].start();

        }

        Thread.sleep(2000);

        t.shutDown();

    }

}

 

當(dāng)調(diào)用t.shutDown()方法將shutDownRequested的值設(shè)置為true以后,因?yàn)閟hutDownRequested 使用了volatile ,所有線程都獲取了它的最新值true,while循環(huán)的條件“!shutDownRequested”不再成立,“ System.out.println("work!");”打印work的代碼也就停止了執(zhí)行。

Volatile還可以用來禁止指令重排序。

什么是指令重排序?

Int num1 = 3;          1

Int num2 = 4;          2

Int num3 = num1+num2;  3

在這段代碼中cpu在執(zhí)行的時(shí)候會(huì)對代碼進(jìn)行優(yōu)化,以達(dá)到更快的執(zhí)行速度,有可能會(huì)交換1處和2處的代碼執(zhí)行的順序,這就是指令重排序。

指令重排序并不是為了執(zhí)行速度不擇手段的任意重排代碼順序,這樣必然會(huì)亂套,重排序必須遵循一定的規(guī)則,1處和2處的代碼之間沒有任何關(guān)系,他們的執(zhí)行順序?qū)Y(jié)果不會(huì)照成任何影響,也就是說1->2>3的執(zhí)行和2->1->3的執(zhí)行最后結(jié)果都為num3=7.我們說1處和2處的操作沒有數(shù)據(jù)依賴性,沒有數(shù)據(jù)依賴性的代碼可以重排序。

再看一下2處和3處的代碼,如果把他們交換順序,結(jié)果會(huì)不一樣,為什么會(huì)不一樣呢?因?yàn)檫@兩處操作都操作了num2這個(gè)變量,并且在第二處操作中修改了num2的值。

如果有兩個(gè)操作操作了同一個(gè)變量,并且其中一個(gè)為寫操作,那么這兩個(gè)操作就存在數(shù)據(jù)依賴性,對于有數(shù)據(jù)依賴性的操作,不能重排序,所以2處和3處的操作不能重排序。

還有一個(gè)規(guī)則是無論怎么重新排序,單線程的執(zhí)行結(jié)果不能被改變,也就是說在單線程的情況下,我們是感受不到重排序帶來的影響的。

在多線程的情況下重排序會(huì)對程序造成什么影響呢?

舉個(gè)栗子:

//定義一個(gè)布爾型的變量表示是否讀取配置文件,初始為未讀取

Volatile boolean flag = false; 1

 

//線程A執(zhí)行 讀取配置文件以后將flag改為true

readConfig(); 2

flag = true; 3

 

//線程B執(zhí)行循環(huán)檢測flag,如果為false表示未讀取配置文件,則休眠。如果為true表示已讀取配置文件,則執(zhí)行doSomething()

while(!flag){ 4

sleep(); 5

}

doSomething(); 6

在這段偽代碼中如果1處的代碼沒有用Volatile關(guān)鍵字,可能由于指令重排序的優(yōu)化,在A線程中,3處的代碼 flag=true在2處代碼之前執(zhí)行,導(dǎo)致B線程在配置文件還未讀取的情況下去執(zhí)行相關(guān)操作,從而引起錯(cuò)誤。

而Volatile關(guān)鍵字可以避免這種情況發(fā)生。

他是如何做到的呢?

通過匯編代碼可以看出,在3處當(dāng)我們對Volatile修飾的變量做賦值操作的時(shí)候,多執(zhí)行了一個(gè)指令 “l(fā)ock add1 $0x0,(%esp)”.

這個(gè)指令的作用是使該指令之后的所有操作不能重排序到該指令的前面,專業(yè)術(shù)語叫做內(nèi)存屏障。

正是因?yàn)閮?nèi)存屏障的存在能夠保證代碼的正確執(zhí)行,所以讀取Volatile關(guān)鍵字修飾的變量和普通變量沒有什么差別,但是做寫入操作的時(shí)候由于要插入內(nèi)存屏障,會(huì)影響到效率。

實(shí)際上在jdk對Synchronized進(jìn)行優(yōu)化以后,Synchronized的性能明顯提升和Volatile已經(jīng)差別不大了,Volatile的用法比較復(fù)雜,容易出錯(cuò),Synchronized也可以解決變量可見性的問題,所以通常情況下我們優(yōu)先選擇Synchronized,但是Synchronized不能禁止指令重排序,貌似這是Volatile的適用場合。

網(wǎng)站標(biāo)題:java中的volatile關(guān)鍵字是什么?volatile關(guān)鍵字怎么用?
文章出自:http://bm7419.com/article4/jcisoe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站網(wǎng)站排名、服務(wù)器托管、全網(wǎng)營銷推廣微信公眾號(hào)、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(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)

網(wǎng)站托管運(yùn)營