JUC之volatile

一、簡介

volatile是Java語言的關鍵字,用來修飾可變變量(即該變量不能被final修飾),且必須是至少類內可見。所以它是可以修飾帶static的變量。這我自己下定義。

它是被設計用來修飾被不同線程訪問和修改的變量。來自 百度百科

二、功能

volatile提供了一個高效的同步機制,她在某些情況下可以代替synchronized實現更輕量和高效的同步機制,同時也更爲脆弱,更難於掌控。被volatile修飾的變量具有內存可見性,但不具有原子性。至於什麼是可見性,前面已經做過簡單介紹,接下來我們進一步來看什麼是可見性。

1. 內存可見性

首先爲什麼會出現內存可見性問題呢?
想完全理解這個問題,請自行閱讀《深入理解計算機系統》吧!這裏簡單說一下,

每個線程都有它自己的線程上下文,包括棧、棧指針、程序計數器、通用目的寄存器和條件碼。所有的運行在一個進程裏的共享該進程的整個虛擬地址空間。——來自《深入理解計算機系統》

下面這個說法可能並不嚴謹,甚至是有誤,但對我們理解這個問題有幫忙。
如你所知,所有計算都發生CPU,然而它直接操作主存的效果比較遠,不如CPU的緩存區,更遠不如寄存器。其次,如上面所有的系統會爲每個線程分配自己的線程上下文。在這兩個大提前下,可能簡化的理解爲線程有自己的高速cache,即所有線程操作變量時,都不會直接操作主存。當發生cache miss時,從主存拷貝到cache,這些都是你懂的啦。跟所有的cache一樣,都存在一致性的問題。

即是正常情況下什麼時候發生cache沖刷回主存並不可控。
不正常情況下,退出臨界區時即刻強制更新主存。另一種情況,即我們要討論的volatile。被volatile修飾的變量比較特殊,表示直接操作主存,不需要通過cache。直接要用時直接從主存取(注意取出來還是會把值放在自己的上下文,這點後面需要用到),用完寫直接回主存。這就是內存可見性

2. 可不完全替代synchronized

之前整理synchronized的時候忘了講synchronized怎麼實現同步的,在這裏順便帶出來吧。
synchronized是通過臨界區實現同步的,臨界區的同步方式是同一個時間只有最多一個線程進入臨界區,也就是說只能保證原臨界區具有原子性。這是什麼意思呢,先來看一下面例子吧。


void barfoo() {
    new Thread(() -> {
            for(int v=0; v<100; v++) bar();
        }).start();

        new Thread(() -> {
            for(int v=0; v<100; v++) foo();
        }).start();
    }
}

int v = 0;
void bar() {
    final int t = v + 1;
    v++;
    try {
        TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(10));
    } catch (InterruptedException e) { }
    if(t != v)System.out.println("not match");
}

synchronized void foo() {
    final int t = v + 1;
    v++;
    try {
        TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(10));
    } catch (InterruptedException e) { }
    if(t != v)System.out.println("not match");
}

執行barfoo()的結果打印了not match
synchronized只是通過線程在離開臨界區時會把線程上下文沖刷回主存,從而實現一致性,但對於變量v而言不具備原子性,更無法保證能夠一致性。

volatile可部分替代synchronized,也就是說在特定條件或者場景下可以替代synchronized。上面我們提到過volatile具有內存可見性,但不具有原子性,而synchronized實際是上能夠實現原子性的。這一點是volatile做不到的,也是這種場景下volatile無法代替synchronized。
這一點就不舉例了,主要知道什麼是原子性和非原子性即可自行實驗了。如:a += b就一個非原子性操作。

三、總結

  1. 簡單的瞭解了volatile的用法;
  2. 進一步瞭解內存可見性和synchronized實現原理;
  3. volatile與synchronized的差異,以及可代替場景;
  4. volatile通過內存可見性實現同步,即線程A操作了被volatile修飾的變量之後,線程B立馬可能讀到線程A的修改結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章