成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

你應(yīng)該知道的 volatile 關(guān)鍵字

calx / 305人閱讀

摘要:線程在工作時,需要將主內(nèi)存中的數(shù)據(jù)拷貝到工作內(nèi)存中。內(nèi)存可見性的應(yīng)用當(dāng)我們需要在兩個線程間依據(jù)主內(nèi)存通信時,通信的那個變量就必須的用來修飾正在運行。。。

前言

不管是在面試還是實際開發(fā)中 volatile 都是一個應(yīng)該掌握的技能。

首先來看看為什么會出現(xiàn)這個關(guān)鍵字。

內(nèi)存可見性

由于 Java 內(nèi)存模型(JMM)規(guī)定,所有的變量都存放在主內(nèi)存中,而每個線程都有著自己的工作內(nèi)存(高速緩存)。

線程在工作時,需要將主內(nèi)存中的數(shù)據(jù)拷貝到工作內(nèi)存中。這樣對數(shù)據(jù)的任何操作都是基于工作內(nèi)存(效率提高),并且不能直接操作主內(nèi)存以及其他線程工作內(nèi)存中的數(shù)據(jù),之后再將更新之后的數(shù)據(jù)刷新到主內(nèi)存中。

這里所提到的主內(nèi)存可以簡單認(rèn)為是堆內(nèi)存,而工作內(nèi)存則可以認(rèn)為是棧內(nèi)存。

如下圖所示:

所以在并發(fā)運行時可能會出現(xiàn)線程 B 所讀取到的數(shù)據(jù)是線程 A 更新之前的數(shù)據(jù)。

顯然這肯定是會出問題的,因此 volatile 的作用出現(xiàn)了:

當(dāng)一個變量被 volatile 修飾時,任何線程對它的寫操作都會立即刷新到主內(nèi)存中,并且會強制讓緩存了該變量的線程中的數(shù)據(jù)清空,必須從主內(nèi)存重新讀取最新數(shù)據(jù)。

volatile 修飾之后并不是讓線程直接從主內(nèi)存中獲取數(shù)據(jù),依然需要將變量拷貝到工作內(nèi)存中。

內(nèi)存可見性的應(yīng)用

當(dāng)我們需要在兩個線程間依據(jù)主內(nèi)存通信時,通信的那個變量就必須的用 volatile 來修飾:

public class Volatile implements Runnable{

    private static volatile boolean flag = true ;

    @Override
    public void run() {
        while (flag){
            System.out.println(Thread.currentThread().getName() + "正在運行。。。");
        }
        System.out.println(Thread.currentThread().getName() +"執(zhí)行完畢");
    }

    public static void main(String[] args) throws InterruptedException {
        Volatile aVolatile = new Volatile();
        new Thread(aVolatile,"thread A").start();


        System.out.println("main 線程正在運行") ;

        TimeUnit.MILLISECONDS.sleep(100) ;

        aVolatile.stopThread();

    }

    private void stopThread(){
        flag = false ;
    }
}

主線程在修改了標(biāo)志位使得線程 A 立即停止,如果沒有用 volatile 修飾,就有可能出現(xiàn)延遲。

但這里有個誤區(qū),這樣的使用方式容易給人的感覺是:

volatile 修飾的變量進行并發(fā)操作是線程安全的。

這里要重點強調(diào),volatile不能保證線程安全性!

如下程序:

public class VolatileInc implements Runnable{

    private static volatile int count = 0 ; //使用 volatile 修飾基本數(shù)據(jù)內(nèi)存不能保證原子性

    //private static AtomicInteger count = new AtomicInteger() ;

    @Override
    public void run() {
        for (int i=0;i<10000 ;i++){
            count ++ ;
            //count.incrementAndGet() ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileInc volatileInc = new VolatileInc() ;
        Thread t1 = new Thread(volatileInc,"t1") ;
        Thread t2 = new Thread(volatileInc,"t2") ;
        t1.start();
        //t1.join();

        t2.start();
        //t2.join();
        for (int i=0;i<10000 ;i++){
            count ++ ;
            //count.incrementAndGet();
        }


        System.out.println("最終Count="+count);
    }
}

當(dāng)我們?nèi)齻€線程(t1,t2,main)同時對一個 int 進行累加時會發(fā)現(xiàn)最終的值都會小于 30000。

這是因為雖然 volatile 保證了內(nèi)存可見性,每個線程拿到的值都是最新值,但 count ++ 這個操作并不是原子的,這里面涉及到獲取值、自增、賦值的操作并不能同時完成。

所以想到達(dá)到線程安全可以使這三個線程串行執(zhí)行(其實就是單線程,沒有發(fā)揮多線程的優(yōu)勢)。

也可以使用 synchronize 或者是鎖的方式來保證原子性。

還可以用 Atomic 包中 AtomicInteger 來替換 int,它利用了 CAS 算法來保證了原子性。

指令重排

內(nèi)存可見性只是 volatile 的其中一個語義,它還可以防止 JVM 進行指令重排優(yōu)化。

舉一個偽代碼:

int a=10 ;//1
int b=20 ;//2
int c= a+b ;//3

一段特別簡單的代碼,理想情況下它的執(zhí)行順序是:1>2>3。但有可能經(jīng)過 JVM 優(yōu)化之后的執(zhí)行順序變?yōu)榱?2>1>3。

可以發(fā)現(xiàn)不管 JVM 怎么優(yōu)化,前提都是保證單線程中最終結(jié)果不變的情況下進行的。

可能這里還看不出有什么問題,那看下一段偽代碼:

private static Map value ;
private static volatile boolean flag = fasle ;

//以下方法發(fā)生在線程 A 中 初始化 Map
public void initMap(){
    //耗時操作
    value = getMapValue() ;//1
    flag = true ;//2
}


//發(fā)生在線程 B中 等到 Map 初始化成功進行其他操作
public void doSomeThing(){
    while(!flag){
        sleep() ;
    }
    //dosomething
    doSomeThing(value);
}

這里就能看出問題了,當(dāng) flag 沒有被 volatile 修飾時,JVM 對 1 和 2 進行重排,導(dǎo)致 value 都還沒有被初始化就有可能被線程 B 使用了。

所以加上 volatile 之后可以防止這樣的重排優(yōu)化,保證業(yè)務(wù)的正確性。

指令重排的的應(yīng)用

一個經(jīng)典的使用場景就是雙重懶加載的單例模式了:

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    //防止指令重排
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

這里的 volatile 關(guān)鍵字主要是為了防止指令重排。

如果不用 ,singleton = new Singleton();,這段代碼其實是分為三步:

分配內(nèi)存空間。(1)

初始化對象。(2)

singleton 對象指向分配的內(nèi)存地址。(3)

加上 volatile 是為了讓以上的三步操作順序執(zhí)行,反之有可能第二步在第三步之前被執(zhí)行就有可能某個線程拿到的單例對象是還沒有初始化的,以致于報錯。

總結(jié)

volatileJava 并發(fā)中用的很多,比如像 Atomic 包中的 value、以及 AbstractQueuedLongSynchronizer 中的 state 都是被定義為 volatile 來用于保證內(nèi)存可見性。

將這塊理解透徹對我們編寫并發(fā)程序時可以提供很大幫助。

號外

最近在總結(jié)一些 Java 相關(guān)的知識點,感興趣的朋友可以一起維護。

地址: https://github.com/crossoverJie/Java-Interview

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68710.html

相關(guān)文章

  • JAVA多線程機制解析-volatile&synchronized

    摘要:當(dāng)一個線程持有重量級鎖時,另外一個線程就會被直接踢到同步隊列中等待。 java代碼先編譯成字節(jié)碼,字節(jié)碼最后編譯成cpu指令,因此Java的多線程實現(xiàn)最終依賴于jvm和cpu的實現(xiàn) synchronized和volatile 我們先來討論一下volatile關(guān)鍵字的作用以及實現(xiàn)機制,每個線程看到的用volatile修飾的變量的值都是最新的,更深入的解釋就涉及到Java的內(nèi)存模型了,我們...

    dendoink 評論0 收藏0
  • 如何使用 volatile, synchronized, final 進行線程間通信

    摘要:如線程執(zhí)行后,線程執(zhí)行,相當(dāng)于線程向線程發(fā)送了消息。我們可以利用這種互斥性來進行線程間通信。 你是否真正理解并會用volatile, synchronized, final進行線程間通信呢,如果你不能回答下面的幾個問題,那就說明你并沒有真正的理解: 對volatile變量的操作一定具有原子性嗎? synchronized所謂的加鎖,鎖住的是什么? final定義的變量不變的到底是什么...

    keithxiaoy 評論0 收藏0
  • volatile 關(guān)鍵字深入分析及AtomicInteger使用

    摘要:我們使用命令查看字節(jié)碼會發(fā)現(xiàn)在虛擬機中這個自增運算使用了條指令。其實這么說也不是最嚴(yán)謹(jǐn)?shù)?,因為即使?jīng)過編譯后的字節(jié)碼只使用了一條指令進行運算也不代表這條指令就是原子操作。 volatile的語義:1、保證被volatile修飾的變量對所有其他的線程的可見性。2、使用volatile修飾的變量禁止指令重排優(yōu)化??创a: public class InheritThreadClass ex...

    raoyi 評論0 收藏0
  • 重拾 Java 基礎(chǔ)

    摘要:阿里開始招實習(xí),同學(xué)問我要不要去申請阿里的實習(xí),我說不去,個人對阿里的印象不好。記得去年阿里給我發(fā)了郵件,我很認(rèn)真地回復(fù),然后他不理我了。 引言 最近好久沒有遇到技術(shù)瓶頸了,思考得自然少了,每天都是重復(fù)性的工作。 阿里開始招實習(xí),同學(xué)問我要不要去申請阿里的實習(xí),我說不去,個人對阿里的印象不好。 記得去年阿里給我發(fā)了郵件,我很認(rèn)真地回復(fù),然后他不理我了。(最起碼的尊重都沒有,就算我菜你起...

    ideaa 評論0 收藏0
  • 并發(fā)編程藝術(shù)

    摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線程修改了的值,但是修改以后,的值可能還沒有寫回到主存中,那么線程得到就是很自然的事了。同理,線程對于的賦值操作也可能沒有及時刷新到主存中。線程的最后操作與線程發(fā)現(xiàn)線程已經(jīng)結(jié)束同步。 很久沒更新文章了,對隔三差五過來刷更新的讀者說聲抱歉。 關(guān)于 Java 并發(fā)也算是寫了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。 本文的...

    curlyCheng 評論0 收藏0

發(fā)表評論

0條評論

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