国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

paulquei / 3232人閱讀

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

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

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

防止指令 重排序

保證對 64 位變量 讀寫的原子性

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

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

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

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

對于 volatile 修飾的變量,JVM 可以保證:

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

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

二. 防止指令重排序

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

對于單例模式,為了達(dá)到延時初始化,并且可以在多線程環(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)行加鎖操作,是個很大的效率浪費(fèi)。

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

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;
    }
}

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

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

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

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

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

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

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

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

所以為了避免對 instance 變量涉及的操作進(jìn)行重排序,保證 “雙重檢測鎖” 的正確性,我們可以將 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;
    }
}
三. 保證對 64 位變量讀寫的原子性

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

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

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

讀取變量到棧中某個位置

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

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

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

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

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

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

    mushang 評論0 收藏0
  • BATJ都愛問線程面試題

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

    高勝山 評論0 收藏0

發(fā)表評論

0條評論

paulquei

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<