Java記憶體模型與volatile關鍵字淺析

來源:互聯網
上載者:User

標籤:技巧   方向   訪問規則   catch   ges   關鍵字   ble   ase   col   

volatile關鍵字在java並發編程中是經常被用到的,大多數朋友知道它的作用:被volatile修飾的共用變數對各個線程可見,volatile保證變數在各線程中的一致性,因而變數在運算中是安全執行緒的。但是經過深入研究發現,大致方向是對的 ,但是細節上不是這樣。

首先,引出volatile的作用。
情景:當線程A遇到某個條件時,希望線程B做某件事。像這樣的情境應該是經常會遇到的吧,下面我們來看一段類比代碼:

package com.jack.jvmstudy;public class TestVolatile extends Thread{    private boolean isRunning = true;//標識線程是否運行    public boolean isRunning() {        return isRunning;    }    public void setRunning(boolean isRunning) {        this.isRunning = isRunning;    }    @Override    public void run() {        while(isRunning()) {            //若 isRunning = true 此處將陷入死迴圈        }        System.out.println("迴圈線程結束...");    }    public static void main(String[] args) {        TestVolatile tv = new TestVolatile();        Thread t1 = new Thread(tv);        t1.start();//啟動線程,此時進入無限的迴圈之中        try {            //讓主線程暫停 1 秒鐘            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        tv.setRunning(false);//主線程將isRunning改為false,想終止迴圈線程        System.out.println("主線程結束...");    }}

運行此段程式發現,主線程結束了,但是迴圈線程依舊在不停地迴圈,這是正確結果,雖然不是我們想看到的結果。我們的目的是想讓主線程終止迴圈線程的執行,但是上面的程式顯然做不到,要達到這種效果,有多種方式,今天我們就看看使用volatile關鍵字,只需要給 isRunning 加上 volatile 即可,然後執行程式,我們發現迴圈線程終止了,是不是很神奇,其實我們都知道這並不神奇,道理也很簡單,就是最上面的那段話,但是再深一點呢?就涉及到了java的記憶體模型。

java記憶體模型:

原諒我的畫圖技巧!!!

java記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣的底層細節。此處的變數與java編程中的變數有所區別,它包括了執行個體欄位,靜態欄位和構成數組對象的元素,但不包括局部變數與方法參數,因為後者是線程私人的,不會被共用,自然就不會存在競爭問題。

下面根據以上的記憶體模型,來看看記憶體間是如何進行互動操作的。
一個新的變數是先在主記憶體中誕生,如果各個線程使用到了這個變數,會在各自的工作空間中保留這個變數的副本。線程修改了副本之後,會立即同步到主記憶體,但是如果沒有經過特別處理,其他線程依舊是原來的那個值,也就是一個到期的值。拿最上面的例子來說,首先共用變數isRunning=true誕生於主記憶體,然後主線程和迴圈線程各自保留了一份副本,然後主線程修改了isRunning的值並同步回主記憶體,但是迴圈線程依舊是原先的值,所以就造成了死迴圈的結果。
接下來就該volatile登場了,被volatile修飾的變數對每個線程可見,意思就是說被volatile修飾的變數,各個線程如果要使用它的話,都會去主記憶體中取最新值,而不是直接使用副本,這樣就保證了此變數在各個線程中的一致性。雖然被volatile修飾的變數能保證各線程都拿到了最新的資料,但是並不代表基於volatile變數的運算在並發下是安全的,為什麼呢?先上代碼

package com.jack.jvmstudy;public class TestVolatile2 {    public volatile static int race = 0;    public static void increase() {        race ++;    }    public static void main(String[] args) {        Thread[] threads = new Thread[20];        for(int i = 0; i < 20; i++) {            threads[i] = new Thread(new Runnable() {                @Override                public void run() {                    for(int i = 0; i< 10000; i++) {                        increase();                    }                }            });            threads[i].start();        }        //等待所有累加線程都結束        while(Thread.activeCount() > 1)            Thread.yield();        System.out.println("race = " + race);    }}

運行上面的程式,我們期望輸出20000,但是執行完之後發現並不是這樣,並且相距很大。為什麼呢?
因為 race ++ 不是原子操作,雖然race被volatile修飾,保證了主記憶體中變數的修改第一時間反映給了各個線程,但是 ++ 操作並不是一步完成的,簡單分析一下,race ++ 操作分為三步,a、擷取race的值;b、race的值加1;c、返回race值。由於volatile的作用,線程每次擷取race的值都是最新的,但是某個線程可能在執行完a之後被掛起了,別的線程完成了race++整個操作,並將值寫入了主記憶體之中,此時這個線程接著執行b操作的時候,race的值已經到期了,再寫入主記憶體的值就小了。很簡單,在increase()方法上加上 synchronized 關鍵字保證 race++是原子操作就行了。

好了,今天關於volatile的分析就到這裡了,這篇博文主要參考《深入理解java虛擬機器》,只是作了簡單的概述,有興趣的朋友可以去閱讀原書。

Java記憶體模型與volatile關鍵字淺析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.