天天看點

Java并發程式設計知識點總結(七)——原子性、有序性、可見性

文章目錄

    • (一)、原子性
        • 1.1 原子性的概念
        • 1.2 原子性操作
        • 1.3 synchronized和volatile對比
    • (二)、有序性
    • (三)、可見性

(一)、原子性

1.1 原子性的概念

原子性指的是一個操作是不可中斷的,要麼全部執行成功,要麼全部執行失敗。

在多線程環境中,如果一個線程開始了操作,那麼其他線程就不能對這個線程有幹擾。

int a = 10;//1
    a++;//2
    int b = a;//3
    a = a + 10;//4
           

分析上面的四行代碼,其實隻有第一行是具有原子性的,直接将10指派給a。剩下的三行都不具有原子性。例如第二行的a++操作,其實是分為3步的。1)、将a的值讀取到工作記憶體中 2)、将a執行+1操作 3)、将計算的值寫回a中。這三個操作就無法構成原子性了。3和4也是同樣的道理。

1.2 原子性操作

Java記憶體模型中定義了一下8個操作是原子性的。

  1. lock(鎖定):作用于主記憶體中的變量,它把一個變量辨別為一個線程獨占的狀态;
  2. unlock(解鎖):作用于主記憶體中的變量,它把一個處于鎖定狀态的變量釋放出來,釋放後的變量才可以被其他線程鎖定
  3. read(讀取):作用于主記憶體的變量,它把一個變量的值從主記憶體傳輸到線程的工作記憶體中,以便後面的load動作使用;
  4. load(載入):作用于工作記憶體中的變量,它把read操作從主記憶體中得到的變量值放入工作記憶體中的變量副本
  5. use(使用):作用于工作記憶體中的變量,它把工作記憶體中一個變量的值傳遞給執行引擎,每當虛拟機遇到一個需要使用到變量的值的位元組碼指令時将會執行這個操作;
  6. assign(指派):作用于工作記憶體中的變量,它把一個從執行引擎接收到的值賦給工作記憶體的變量,每當虛拟機遇到一個給變量指派的位元組碼指令時執行這個操作;
  7. store(存儲):作用于工作記憶體的變量,它把工作記憶體中一個變量的值傳送給主記憶體中以便随後的write操作使用;
  8. write(操作):作用于主記憶體的變量,它把store操作從工作記憶體中得到的變量的值放入主記憶體的變量中。

    注意:java記憶體模型隻要求上述操作是順序執行的,而不一定是連續執行的

    long和double不是原子性操作

1.3 synchronized和volatile對比

synchronized
           

上面的8種基本操作包括了6種滿足基本讀寫的原子性操作,還剩下lock和unlock兩個原子操作。synchronized則使用更高層次的指令monitorenter和monitorexit進行加鎖和解鎖,是以synchronized是滿足原子性的。

volatile
           
public class Thread15 {

    static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for(int i = 0; i < 1000; i++) {
                        count++;
                    }
                }
            };
            thread.start();
        }
        Thread.sleep(5000);
        System.out.println(count);
    }
}

           
Java并發程式設計知識點總結(七)——原子性、有序性、可見性

我們根據上面的代碼來看,雖然共享變量count被修飾為volatile,但結果卻不是預計的10000。這其實就可以說明volatile是無法保證原子性的。 因為count++并不是原子性的。如果A線程讀取count到工作記憶體,B線程已經完成了自增的操作,那麼A線程讀取的值就是過期的值。是以volatile是無法保證原子性的。

如果想讓volatile擁有原子性:

  1. 運算結果的值不依賴于變量的目前值或者隻有一個線程修改該變量的值
  2. 變量不需要與其他的狀态變量參與不變限制。例如low<up這樣的場景就不行,例如
volatile boolean judge = false;
    
    public void a(){
        judge = true;
    }
    
    public void b() {
        while(judge == false) {
            //do something
        }
    } 
           

(二)、有序性

synchronized
           

Synchronized關鍵字隻允許一個線程通路同步代碼塊,其他線程隻能阻塞等待,也就是說每個線程隻能串行執行,是以Synchronized是具有原子性的。

volatile
           
private   Thread16() {}
    private volatile static Thread16 thread16;

    public Thread16 getThread16() {
        if(thread16 == null) {
            synchronized (Thread16.class) {
                if (thread16 == null) {
                    thread16 = new Thread16();
                }
            }
        }
        return thread16;
    }
           

上面是我們常見的雙檢驗單例模式。

上面的代碼其實分為3步操作:1)、配置設定記憶體空間 2)、初始化對象 3)、thread16指向記憶體空間

但是有可能出線重排序問題。

Java并發程式設計知識點總結(七)——原子性、有序性、可見性

如果如上圖中,如果還沒有初始化對象的時候就已經判斷了是否thread16為null,也就是3操作在2操作之前執行,那麼單例模式就會失敗,如果不是使用雙重檢驗的話。但是使用volatile修飾之後,可以禁止2和3的重排序,進而避免這種情況。由此可見,volatile是具有可見性的。

(三)、可見性

synchronized
           

通過之前的文章:Java并發程式設計知識點總結(四)——Synchronized實作原理以及優化,我們可以知道Synchonized在執行讀寫操作的時候會強制從主記憶體中讀取資料,并且也會強制将寫操作後的資料更新到主記憶體中。是以具有可見性。

volatile
           

通過之前的文章:Java并發程式設計知識點總結(五)——volatile實作原理,我們可以知道對于volatile的寫操作,會導緻其他處理器的緩存失效,進而強制從主記憶體中讀取資料來更新資料,是以volatile是具有可見性的。

參考了這篇文章:三大性質總結:原子性,有序性,可見性

繼續閱讀