java併發編程,volatile內存實現和原理

前面的博文說了java的內存模型,介紹了java內存模型的基礎,此篇文章來講一下volatile關鍵字,這個在併發編程中,佔有舉足輕重地位的關鍵字。java

在java5.0 以前它是一個備受爭議的關鍵字,5以後它重獲新生。volatile關鍵字的做用是保證多線程中變量的可見性,是JUC包中的核心。編程

在內存模型基礎中,已經提到過,JVM是分爲堆內存和棧內存的,堆內存在線程之間共享,而棧內存爲線程內部私有,對其餘線程不可見。爲保證變量的可見性,可使用volatile修飾,那爲何使用了關鍵字volatile修飾後,就能保證可見性,下面進行分析。緩存

volatile 能夠看做是一個輕量級的鎖,這麼說多是不許確的,但它確實具有了鎖的一些特性。與鎖的區別是,鎖保證原子性,鎖住的多是是個變量或者一段代碼,而volatile修飾的變量只能保證變量在線程之間的傳遞,只能保證可見性,在一些方面並無具有原子性多線程

因此上面的話有兩層語義:併發

  • 保證可見性,不保證原子性
  • 禁止指令的重排序(重排序會破壞volatile的內存語義)

volatile變量的讀/寫,能夠實現線程之間的通訊。ide

volatile內存語義

  • 寫的內存語義: 當寫一個volatile變量時,JMM會把線程對應的本地內存中的共享變量刷新到主內存。
  • 讀的內存語義: 當讀一個volatile變量時,JMM會把線程對應的本地內存置爲無效,線程接下來從主內存中讀取共享變量。

初始時,兩個線程的本地內存中的flag和a都是初始狀態,線程A在寫flag變量後,本地內存A中更新過的兩個共享變量的值被刷新到主內存中,在讀flag變量後,本地內存中包含的值已經被置爲無效,此時線程B必須從主內存中讀取共享變量。性能

volatile內存語義實現

爲了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障,來禁止特定類型的處理器重排序。JMM採用了保守策略,規則以下:spa

  • 在每一個volatile寫操做的前面插入一個StoreStore屏障
  • 在每一個volatile寫操做的後面插入一個StoreLoad屏障
  • 在每一個volatile讀操做的前面插入一個LoadLoad屏障
  • 在每一個volatile讀操做的後面插入一個LoadStore屏障

從彙編層看volatile關鍵字

記得曾經看過一篇文章,講述的是volatile關鍵字修飾的變量,編程彙編代碼後,會在變量的前面插入一條LOCK指令。.net

Java代碼: instance = new Singleton();//instance是volatile變量

彙編代碼: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

經過上面的代碼發現,volatile修飾的變量會多出一個lock指令,LOCK指令屬於系統層級: LOCK前綴會使處理器執行當前指令時產生一個LOCK#信號,顯示的鎖定總線。線程

來看一下LOCK指令的做用:

  • 鎖總線:其餘cpu對內存的讀寫請求會被阻塞,直到鎖釋放,不過由於鎖總線的的開銷太大,後來採用鎖緩存來代替鎖總線
  • lock後的寫操做會回寫已修改的數據,同時讓其它cpu相關緩存行失效,從而從新從主存中加載最新的數據
  • 不是內存屏障卻完成相似內存屏障的功能,阻止屏障兩邊的執行重排序

一個示例

先啓動一個線程thread,因爲isOver=fasle,因此thread裏的run方法裏的while是一個死循環,而後企圖在main線程裏改變標誌位isOver=true,從而想終止thread裏的while死循環,使得thread線程可以結束退出。但是,事與願違,實驗現象是沒有中止,即thread裏isOver依然爲false仍是處於死循環。

public class VolatileDemo {
    private static boolean isOver = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isOver) ;
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isOver = true;
    }
}

那麼是爲何了?怎樣解決這種問題。

更正後的代碼爲:

public class VolatileDemo {
    private static volatile boolean isOver = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isOver) ;
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isOver = true;
    }
}

注意不一樣點,如今已經將isOver設置成了volatile變量,這樣在main線程中將isOver改成了true後,thread的工做內存該變量值就會失效,從而須要再次從主內存中讀取該值,如今可以讀出isOver最新值爲true從而可以結束在thread裏的死循環,從而可以順利中止掉thread線程。如今問題也解決了,知識也學到了:)。

總結

volatile是一種比鎖更輕量級的線程之間通訊的機制,volatile僅僅保證對單個volatile變量的讀/寫具備原子性,而鎖的互斥執行的特性能夠保證對整個臨界區代碼執行具備原子性,在功能上,鎖比voatile更強大,在可伸縮性和執行性能上,volatile更有優點,可是volatile並不能代替鎖。

應用場景

  • 狀態標記變量
  • double check

參考:

java併發編程藝術

http://blog.csdn.net/eff666/article/details/67640648

相關文章
相關標籤/搜索