成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java 多線程(6):volatile 關(guān)鍵字的使用

paulquei / 3278人閱讀

摘要:所以多線程條件下使用關(guān)鍵字的前提是對(duì)變量的寫操作不依賴于變量的當(dāng)前值,而賦值操作很明顯滿足這一前提。在多線程環(huán)境下,正確使用關(guān)鍵字可以比直接使用更加高效而且代碼簡(jiǎn)潔,但是使用關(guān)鍵字也更容易出錯(cuò)。

volatile 作為 Java 語(yǔ)言的一個(gè)關(guān)鍵字,被看作是輕量級(jí)的 synchronized(鎖)。雖然 volatile 只具有synchronized 的部分功能,但是一般使用 volatile 會(huì)比使用 synchronized 更有效率。在編寫多線程程序的時(shí)候,volatile 修飾的變量能夠:

保證內(nèi)存 可見(jiàn)性

防止指令 重排序

保證對(duì) 64 位變量 讀寫的原子性

一. 保證內(nèi)存可見(jiàn)性

JVM 中,每個(gè)線程都擁有自己棧內(nèi)存,用來(lái)保存當(dāng)前線程運(yùn)行過(guò)程中的變量數(shù)據(jù);然后多個(gè)線程之間共享堆內(nèi)存(也稱主存)。當(dāng)線程需要訪問(wèn)一個(gè)變量時(shí),首先將其從堆內(nèi)存中復(fù)制到自己的棧內(nèi)存作為副本,然后線程每次對(duì)該變量的操作,都將是對(duì)棧中的副本進(jìn)行操作 —— 在某些時(shí)刻(比如退出 synchronized 塊或線程結(jié)束),線程會(huì)將棧中副本的值寫回到主存,此時(shí)主存中的變量才會(huì)被替換為副本的值。這樣自然就帶來(lái)一個(gè)問(wèn)題,即如果兩個(gè)線程共享一個(gè)變量,線程A 改變了變量的值,但是 線程B 可能無(wú)法立即發(fā)現(xiàn)。比如下面這個(gè)經(jīng)典的例子:

public class ConcurrentTest {

    private static boolean running = true;

    public static class AnotherThread extends Thread {

        @Override
        public void run() {
            System.out.println("AnotherThread is running");

            while (running) { }

            System.out.println("AnotherThread is stoped");
        }

    }

    public static void main(String[] args) throws Exception {
        new AnotherThread ().start();

        Thread.sleep(1000);
        running = false;  // 1 秒之后想停止 AnotherThread 
    }
}

上面這段代碼一般情況下都會(huì)死鎖,就是因?yàn)樵?main 方法(主線程)中對(duì) running 做的修改,并不能立馬對(duì) AnotherThread 可見(jiàn)。

如果將 running 加上修飾符 volatile,那么便可以獲取實(shí)際希望的結(jié)果,因?yàn)榇藭r(shí)主線程中設(shè)置 runningfalse 之后,AnotherThread 可以立馬發(fā)現(xiàn) running 的值發(fā)生了改變:

對(duì)于 volatile 修飾的變量,JVM 可以保證:

每次對(duì)該變量的寫操作,都將立即同步到主存;

每次對(duì)該變量的讀操作,都將從主存讀取,而不是線程棧

二. 防止指令重排序

如果一個(gè)操作不是原子操作,那么 JVM 便可能會(huì)對(duì)該操作涉及的指令進(jìn)行 重排序。重排序即在不改變程序語(yǔ)義的前提下,通過(guò)調(diào)整指令的執(zhí)行順序,盡可能達(dá)到提高運(yùn)行效率的目的。

對(duì)于單例模式,為了達(dá)到延時(shí)初始化,并且可以在多線程環(huán)境下使用,我們可以直接使用 synchronized 關(guān)鍵字:

public class Singleton {

    public static Singleton instance = null;

    private Singleton() { }

    public synchronized static Singleton getSingleton() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }
}

這樣做的缺陷也很明顯,那就是 instance 初始化完畢之后,以后每次獲取 instance 仍然需要進(jìn)行加鎖操作,是個(gè)很大的效率浪費(fèi)。

于是出現(xiàn)了一種經(jīng)典寫法叫 “雙重檢測(cè)鎖”:

public class Singleton {

    public static Singleton instance = null;

    private Singleton() { }

    public static Singleton getSingleton() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

但是這樣的寫法同樣會(huì)存在問(wèn)題,因?yàn)?instance = new Singleton() 并非原子操作,其大概可以等同于執(zhí)行:

分配一個(gè) Singleton 對(duì)應(yīng)的內(nèi)存

初始化這個(gè) Singleton 對(duì)應(yīng)的內(nèi)存

instance 指向?qū)?yīng)的內(nèi)存的地址

其中,2 依賴于 1,但是 3 并不依賴于 2 —— 所以,存在 JVM 將這三條語(yǔ)句重排序?yàn)?1->3->2 的可能,即變?yōu)椋?/p>

a. 分配一個(gè) Singleton 對(duì)應(yīng)的內(nèi)存
b.instance 指向?qū)?yīng)的內(nèi)存的地址
c. 初始化這個(gè) Singleton 對(duì)應(yīng)的內(nèi)存

此時(shí)如果 線程A 執(zhí)行完 b,那么此時(shí)的 instance 指向的內(nèi)存并不為 null,然而這塊內(nèi)存卻還沒(méi)有被初始化。當(dāng) 線程B 此時(shí)判斷第一個(gè) if (instance == null) 時(shí)發(fā)現(xiàn) instance 并不為 null,便會(huì)將此時(shí)的 instance 返回 —— 但 Singleton 的初始化可能并未完成,此時(shí) 線程B 使用 instance 便可能會(huì)出現(xiàn)錯(cuò)誤。

在 JDK 1.5 之后,增強(qiáng)了 volatile 的語(yǔ)義,嚴(yán)格限制 JVM (編譯器、處理器)不能對(duì) volatile 修飾的變量涉及的操作指令進(jìn)行重排序。

所以為了避免對(duì) instance 變量涉及的操作進(jìn)行重排序,保證 “雙重檢測(cè)鎖” 的正確性,我們可以將 instance 使用 volatile 修飾:

public class Singleton {

    /* 使用 volatile 修飾 */
    public static volatile Singleton instance = null;

    private Singleton() { }

    public static Singleton getSingleton() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}
三. 保證對(duì) 64 位變量讀寫的原子性

JVM 可以保證對(duì) 32位 數(shù)據(jù)讀寫的原子性,但是對(duì)于 longdouble 這樣 64位 的數(shù)據(jù)的讀寫,會(huì)將其分為 高32位 和 低32位 分兩次讀寫。所以對(duì)于longdouble 的讀寫并不是原子性的,這樣在并發(fā)程序中共享 longdouble 變量就可能會(huì)出現(xiàn)問(wèn)題,于是 JVM 提供了 volatile 關(guān)鍵字來(lái)解決這個(gè)問(wèn)題:

使用 volatile 修飾的 longdouble 變量,JVM 可以保證對(duì)其讀寫的原子性。

但值得注意的是,此處的 “寫” 僅指對(duì) 64位 的變量進(jìn)行直接賦值。而對(duì)于 i++ 這個(gè)語(yǔ)句,事實(shí)上涉及了 讀取-修改-寫入 三個(gè)操作:

讀取變量到棧中某個(gè)位置

對(duì)棧中該位置的值進(jìn)行自增

將自增后的值寫回到變量對(duì)應(yīng)的存儲(chǔ)位置

因此哪怕變量 i 使用 volatile 修飾,也并不能使涉及上面三個(gè)操作的 i++ 具有原子性。所以多線程條件下使用 volatile 關(guān)鍵字的前提是:對(duì)變量的寫操作不依賴于變量的當(dāng)前值,而賦值操作很明顯滿足這一前提。

在多線程環(huán)境下,正確使用 volatile 關(guān)鍵字可以比直接使用 synchronized 更加高效而且代碼簡(jiǎn)潔,但是使用 volatile 關(guān)鍵字也更容易出錯(cuò)。所以,除非十分清楚 volatile 的使用場(chǎng)景,否則還是應(yīng)該選擇更加具有保障性的 synchronized。

Brian Goetz 大大寫過(guò)一篇 “volatile 變量使用指南”,有興趣的讀者可以參閱:Java 理論與實(shí)踐: 正確使用 Volatile 變量

volatile 變量的底層實(shí)現(xiàn)原理,有興趣的讀者可以參閱:

http://www.infoq.com/cn/artic...

http://www.cnblogs.com/paddix...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/66780.html

相關(guān)文章

  • Java線程學(xué)習(xí)(三)volatile關(guān)鍵字

    摘要:三關(guān)鍵字能保證原子性嗎并發(fā)編程藝術(shù)這本書上說(shuō)保證但是在自增操作非原子操作上不保證,多線程編程核心藝術(shù)這本書說(shuō)不保證。多線程訪問(wèn)關(guān)鍵字不會(huì)發(fā)生阻塞,而關(guān)鍵字可能會(huì)發(fā)生阻塞關(guān)鍵字能保證數(shù)據(jù)的可見(jiàn)性,但不能保證數(shù)據(jù)的原子性。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchroniz...

    tain335 評(píng)論0 收藏0
  • 慕課網(wǎng)_《細(xì)說(shuō)Java線程之內(nèi)存可見(jiàn)性》學(xué)習(xí)總結(jié)

    時(shí)間:2017年07月09日星期日說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:無(wú)學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:課程簡(jiǎn)介 1-1 課程簡(jiǎn)介 課程目標(biāo)和學(xué)習(xí)內(nèi)容 共享變量在線程間的可見(jiàn)性 synchronized實(shí)現(xiàn)可見(jiàn)性 volatile實(shí)現(xiàn)可見(jiàn)性 指令重排序 as-if-seria...

    wupengyu 評(píng)論0 收藏0
  • JAVA并發(fā)編程之-Volatile關(guān)鍵字及內(nèi)存可見(jiàn)性

    摘要:的缺點(diǎn)頻繁刷新主內(nèi)存中變量,可能會(huì)造成性能瓶頸不具備操作的原子性,不適合在對(duì)該變量的寫操作依賴于變量本身自己。 作者:畢來(lái)生微信:878799579 1. 什么是JUC? JUC全稱 java.util.concurrent 是在并發(fā)編程中很常用的實(shí)用工具類 2.Volatile關(guān)鍵字 1、如果一個(gè)變量被volatile關(guān)鍵字修飾,那么這個(gè)變量對(duì)所有線程都是可見(jiàn)的。2、如果某條線程修...

    xcold 評(píng)論0 收藏0
  • 深入理解volatile類型——從Java虛擬機(jī)內(nèi)存模型角度

    摘要:本文從內(nèi)存模型角度,探討的實(shí)現(xiàn)原理。通過(guò)共享內(nèi)存或者消息通知這兩種方法,可以實(shí)現(xiàn)通信或同步?;诠蚕韮?nèi)存的線程通信是隱式的,線程同步是顯式的而基于消息通知的線程通信是顯式的,線程同步是隱式的。鎖規(guī)則鎖的解鎖,于于鎖的獲取或加鎖。 一、前言 在java多線程編程中,volatile可以用來(lái)定義輕量級(jí)的共享變量,它比synchronized的使用成本更低,因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)...

    mushang 評(píng)論0 收藏0
  • BATJ都愛(ài)問(wèn)線程面試題

    摘要:今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線程面試題,希望對(duì)大家學(xué)習(xí)和面試都能有所幫助。指令重排在單線程環(huán)境下不會(huì)出先問(wèn)題,但是在多線程環(huán)境下會(huì)導(dǎo)致一個(gè)線程獲得還沒(méi)有初始化的實(shí)例。使用可以禁止的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。 下面最近發(fā)的一些并發(fā)編程的文章匯總,通過(guò)閱讀這些文章大家再看大廠面試中的并發(fā)編程問(wèn)題就沒(méi)有那么頭疼了。今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線...

    高勝山 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<