三大性質總結:原子性、可見性以及有序性

原創文章&經驗總結&從校招到A廠一路陽光一路滄桑

詳情請戳www.coderccc.com

# 1. 三大性質簡介 #
在併發編程中分析線程安全的問題時往往需要切入點,那就是兩大核心:JMM抽象內存模型以及happens-before規則(在這篇文章中已經經過了),三條性質:原子性,有序性和可見性。關於synchronizedvolatile已經討論過了,就想着將併發編程中這兩大神器在 原子性,有序性和可見性上做一個比較,當然這也是面試中的高頻考點,值得注意。

2. 原子性

原子性是指一個操作是不可中斷的,要麼全部執行成功要麼全部執行失敗,有着“同生共死”的感覺。及時在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程所幹擾。我們先來看看哪些是原子操作,哪些不是原子操作,有一個直觀的印象:

int a = 10; //1

a++; //2

int b=a; //3

a = a+1; //4

上面這四個語句中只有第1個語句是原子操作,將10賦值給線程工作內存的變量a,而語句2(a++),實際上包含了三個操作:1. 讀取變量a的值;2:對a進行加一的操作;3.將計算後的值再賦值給變量a,而這三個操作無法構成原子操作。對語句3,4的分析同理可得這兩條語句不具備原子性。當然,java內存模型中定義了8中操作都是原子的,不可再分的。

  1. lock(鎖定):作用於主內存中的變量,它把一個變量標識爲一個線程獨佔的狀態;
  2. unlock(解鎖):作用於主內存中的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定
  3. read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便後面的load動作使用;
  4. load(載入):作用於工作內存中的變量,它把read操作從主內存中得到的變量值放入工作內存中的變量副本
  5. use(使用):作用於工作內存中的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時將會執行這個操作;
  6. assign(賦值):作用於工作內存中的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作;
  7. store(存儲):作用於工作內存的變量,它把工作內存中一個變量的值傳送給主內存中以便隨後的write操作使用;
  8. write(操作):作用於主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中。

上面的這些指令操作是相當底層的,可以作爲擴展知識面掌握下。那麼如何理解這些指令了?比如,把一個變量從主內存中複製到工作內存中就需要執行read,load操作,將工作內存同步到主內存中就需要執行store,write操作。注意的是:java內存模型只是要求上述兩個操作是順序執行的並不是連續執行的。也就是說read和load之間可以插入其他指令,store和writer可以插入其他指令。比如對主內存中的a,b進行訪問就可以出現這樣的操作順序:read a,read b, load b,load a

由原子性變量操作read,load,use,assign,store,write,可以大致認爲基本數據類型的訪問讀寫具備原子性(例外就是long和double的非原子性協定)

synchronized

上面一共有八條原子操作,其中六條可以滿足基本數據類型的訪問讀寫具備原子性,還剩下lock和unlock兩條原子操作。如果我們需要更大範圍的原子性操作就可以使用lock和unlock原子操作。儘管jvm沒有把lock和unlock開放給我們使用,但jvm以更高層次的指令monitorenter和monitorexit指令開放給我們使用,反應到java代碼中就是—synchronized關鍵字,也就是說synchronized滿足原子性

volatile
我們先來看這樣一個例子:

public class VolatileExample {
    private static volatile int counter = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++)
                        counter++;
                }
            });
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}

開啓10個線程,每個線程都自加10000次,如果不出現線程安全的問題最終的結果應該就是:10*10000 = 100000;可是運行多次都是小於100000的結果,問題在於 volatile並不能保證原子性,在前面說過counter++這並不是一個原子操作,包含了三個步驟:1.讀取變量counter的值;2.對counter加一;3.將新值賦值給變量counter。如果線程A讀取counter到工作內存後,其他線程對這個值已經做了自增操作後,那麼線程A的這個值自然而然就是一個過期的值,因此,總結果必然會是小於100000的。

如果讓volatile保證原子性,必須符合以下兩條規則:

  1. 運算結果並不依賴於變量的當前值,或者能夠確保只有一個線程修改變量的值;
  2. 變量不需要與其他的狀態變量共同參與不變約束

3. 有序性

synchronized

synchronized語義表示鎖在同一時刻只能由一個線程進行獲取,當鎖被佔用後,其他線程只能等待。因此,synchronized語義就要求線程在訪問讀寫共享變量時只能“串行”執行,因此synchronized具有有序性

volatile

在java內存模型中說過,爲了性能優化,編譯器和處理器會進行指令重排序;也就是說java程序天然的有序性可以總結爲:如果在本線程內觀察,所有的操作都是有序的;如果在一個線程觀察另一個線程,所有的操作都是無序的。在單例模式的實現上有一種雙重檢驗鎖定的方式(Double-checked Locking)。代碼如下:

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

這裏爲什麼要加volatile了?我們先來分析一下不加volatile的情況,有問題的語句是這條:

instance = new Singleton();

這條語句實際上包含了三個操作:1.分配對象的內存空間;2.初始化對象;3.設置instance指向剛分配的內存地址。但由於存在重排序的問題,可能有以下的執行順序:

不加volatile可能的執行時序

如果2和3進行了重排序的話,線程B進行判斷if(instance==null)時就會爲true,而實際上這個instance並沒有初始化成功,顯而易見對線程B來說之後的操作就會是錯得。而用volatile修飾的話就可以禁止2和3操作重排序,從而避免這種情況。volatile包含禁止指令重排序的語義,其具有有序性

4. 可見性

可見性是指當一個線程修改了共享變量後,其他線程能夠立即得知這個修改。通過之前對synchronzed內存語義進行了分析,當線程獲取鎖時會從主內存中獲取共享變量的最新值,釋放鎖的時候會將共享變量同步到主內存中。從而,synchronized具有可見性。同樣的在volatile分析中,會通過在指令中添加lock指令,以實現內存可見性。因此, volatile具有可見性

5. 總結

通過這篇文章,主要是比較了synchronized和volatile在三條性質:原子性,可見性,以及有序性的情況,歸納如下:

synchronized: 具有原子性,有序性和可見性
volatile:具有有序性和可見性

參考文獻

《java併發編程的藝術》
《深入理解java虛擬機》

發佈了40 篇原創文章 · 獲贊 38 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章