并發(fā)線程-volatile關(guān)鍵字

1吊说、結(jié)論

volatile具有可見性防止指令重排的能力曙咽,但是在某些場景下不能保證線程安全(無法替代synchronized關(guān)鍵字)

2去枷、原因簡析

1久免、線程安全問題中有三個概念:原子性(Atomicity)底瓣、可見性(Visibility)谢揪、有序性(Ordering)。
2捐凭、synchronized關(guān)鍵字可以保證原子性拨扶、可見性和有序性;volatile只能保證可見性和有序性(有序性體現(xiàn)在 防止指令重排 上)茁肠。
3患民、使用volatile修飾的(多線程共享的)變量進(jìn)行的是原子的修改操作時,這時volatile可以保證線程安全垦梆;除此之外匹颤,單一地使用volatile不保證線程安全。
4托猩、volatile會對總線(主存)加上LOCK前綴指令(觀察匯編源碼得知)印蓖,LOCK不是內(nèi)存屏障,但是完成的事情是類似內(nèi)存屏障(也叫內(nèi)存柵欄)的功能京腥。LOCK可以理解成是CPU一級的鎖赦肃,加上LOCK后,其他CPU對該內(nèi)存地址的原子的讀寫請求都會被阻塞公浪,直到鎖釋放他宛。(《碼出高效Java開發(fā)手冊》P232中描述使用了volatile后“...任何對此變量的操作都會在內(nèi)存中進(jìn)行,不會產(chǎn)生副本”欠气,筆者認(rèn)為描述有問題)
5厅各、單一地使用synchronized(來保證線程安全)會有一定的效能損耗,可以用volatile搭配使用synchronized減少(因為要保證線程安全帶來的)效能損耗晃琳,也可以搭配CAS(比如自旋鎖運用了CAS -- Compare-And-Swap)讯检。

3、背景

計算機(jī)在對內(nèi)存進(jìn)行操作時卫旱,會存在主內(nèi)存(有些地方叫物理內(nèi)存)和高速緩存的概念人灼。主存中的變量值對所有線程可見,高速緩存是線程私有的--對其他線程不可見的顾翼。CPU對內(nèi)存進(jìn)行操作的時候投放,單個線程會從主存(總線)中讀取目標(biāo)內(nèi)存地址中的數(shù)據(jù),copy到高速緩存(作為副本)适贸,后續(xù)的一系列操作都是基于這個“副本”灸芳,操作完后涝桅,將副本的值同步回主存。
內(nèi)存柵欄實現(xiàn)了 可見性 和 防止指令重排 的效果


內(nèi)存柵欄/內(nèi)存屏障

4烙样、代碼驗證

4.1冯遂、這是一段線程不安全的代碼

public class Test {

    public static volatile int inc = 0;

    public static void increase() throws InterruptedException {
        inc++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++){
                    try {
                        increase();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("----A過程---" + Test.inc);
                }
            }

        });
        Thread thread2 = new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++){
                    try {
                        increase();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("----B過程---" + Test.inc);
                }
            }

        });
        thread1.start();
        thread2.start();
        Thread.sleep(2000);
        System.out.println("--終態(tài)--" + inc);
    }
}

4.2、對#4.1代碼的優(yōu)化

·#4.1的代碼不能復(fù)現(xiàn)出問題谒获,猜測可能是機(jī)器的CPU性能較好蛤肌。所以優(yōu)化了下代碼,如下

public class Test {

        public static volatile int inc = 0;

        public static void increase() throws InterruptedException {
            Thread.sleep(1);
            inc ++;
        }

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


            for (int k=0;k<10;k++){
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        for (int i=0 ; i < 500; i++){
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        Thread.sleep(2);
                                        increase();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                    System.out.println("----A過程---" + inc);
                                }
                            }).start();
                        }
                    }

                }).start();
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        for (int i=0 ; i < 500; i++){
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        Thread.sleep(2);
                                        increase();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                    System.out.println("----B過程---" + inc);
                                }
                            }).start();
                        }
                    }

                }).start();
            }
            Thread.sleep(5000);
            System.out.println("--終態(tài)--" + inc);
        }
}

4.3批狱、#4.2的運行結(jié)果

預(yù)期結(jié)果是10,000裸准,實際運行結(jié)果<10,000

4.4、原因

因為#4.1和#4.2的模型一樣赔硫,#4.1的邏輯更簡單炒俱,故以#4.1為例講

4.4.1、首先爪膊,問題出在這一行


inc++

4.4.2权悟、其次,inc++非原子操作


inc++ 即 inc = inc + 1

4.4.3惊完、出現(xiàn)異常(結(jié)果不合預(yù)期)的情況
step-1
step-2
step-3
step-4
step-5

4.5僵芹、反思

從結(jié)果看來,在這個場景中小槐,volatile沒有發(fā)揮任何作用嘛?
我們?nèi)サ?4.2代碼中的volatile關(guān)鍵字荷辕,發(fā)現(xiàn)結(jié)果也是少于10,000


沒有volatile修飾inc變量的情況

我認(rèn)為凿跳,volatile還是發(fā)揮作用的(只是沒有它沒有讓結(jié)果達(dá)到預(yù)期),舉個例子

去掉volatile后疮方,step-4的線程B不是無效掉前兩步的操作控嗜,而是將自己的副本(inc=2)更新到主存中,這時主存中的inc值又被更新了一次(2 -> 2);
假設(shè)在線程競爭中骡显,線程B獲得的CPU時間片輪遠(yuǎn)少于線程A時疆栏,當(dāng)線程A對inc更新過好幾輪了后(假設(shè)此時主存中的inc=4),線程B仍然對主存更新為2。這時主存中的inc值經(jīng)歷了幾個階段


主存中inc的幾個階段

4.6惫谤、比較明確地體現(xiàn)volatile的可見性作用的例子

1壁顶、狀態(tài)標(biāo)記量

volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

2、雙重檢測鎖

class Singleton{
    private volatile static Singleton instance;
     
    private Singleton() {}
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

5溜歪、volatile適用的場景

1)對變量的寫操作不依賴于當(dāng)前值

2)該變量沒有包含在具有其他變量的不變式中

針對這兩點約束若专,個人還不是很理解,具體參考# volatile的適用場景

6蝴猪、參考來源

1调衰、 Java并發(fā)編程:volatile關(guān)鍵字解析
2膊爪、 volatile 和 內(nèi)存屏障
3、《碼出高效Java開發(fā)手冊》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嚎莉,一起剝皮案震驚了整個濱河市米酬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趋箩,老刑警劉巖赃额,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阁簸,居然都是意外死亡爬早,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門启妹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筛严,“玉大人,你說我怎么就攤上這事饶米〗翱校” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵檬输,是天一觀的道長照瘾。 經(jīng)常有香客問我,道長丧慈,這世上最難降的妖魔是什么析命? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮逃默,結(jié)果婚禮上鹃愤,老公的妹妹穿的比我還像新娘。我一直安慰自己完域,他們只是感情好软吐,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吟税,像睡著了一般凹耙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肠仪,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天肖抱,我揣著相機(jī)與錄音,去河邊找鬼藤韵。 笑死虐沥,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欲险,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼镐依,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了天试?” 一聲冷哼從身側(cè)響起槐壳,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喜每,沒想到半個月后务唐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡带兜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年枫笛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刚照。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刑巧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出无畔,到底是詐尸還是另有隱情啊楚,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布浑彰,位于F島的核電站恭理,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏郭变。R本人自食惡果不足惜颜价,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诉濒。 院中可真熱鬧拍嵌,春花似錦、人聲如沸循诉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茄猫。三九已至,卻和暖如春困肩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锌畸。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人比默。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像命咐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子醋奠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355