併發機制的底層實現

concurrent 包的實現

由於 Java 的 CAS 同時具有 volatile 讀和 volatile 寫的內存語義,因此 Java 線程之間的通信現在有了下面四種方式:

  1. A 線程寫 volatile 變量,隨後 B 線程讀這個 volatile 變量。

  2. A 線程寫 volatile 變量,隨後 B 線程用 CAS 更新這個 volatile 變量。

  3. A 線程用 CAS 更新一個 volatile 變量,隨後 B 線程用 CAS 更新這個 volatile 變量。

  4. A 線程用 CAS 更新一個 volatile 變量,隨後 B 線程讀這個 volatile 變量。

同時,volatile 變量的讀/寫和 CAS 可以實現線程之間的通信。把這些特性整合在一起,就形成了整個 concurrent 包得以實現的基石。如果我們仔細分析 concurrent 包的源代碼實現,會發現一個通用化的實現模式:

首先,聲明共享變量爲 volatile;

然後,使用 CAS 的原子條件更新來實現線程之間的同步;

同時,配合以 volatile 的讀/寫和 CAS 所具有的 volatile 讀和寫的內存語義來實現線程之間的通信。

AQS,非阻塞數據結構和原子變量類(Java.util.concurrent.atomic 包中的類),這些 concurrent 包中的基礎類都是使用這種模式來實現的,而 concurrent 包中的高層類又是依賴於這些基礎類來實現的。從整體來看,concurrent 包的實現示意圖如下:

synchronized

synchronized 的要點

關鍵字 synchronized 可以保證在同一個時刻,只有一個線程可以執行某個方法或者某個代碼塊。

synchronized 有 3 種應用方式:

  1. 同步實例方法

  2. 同步靜態方法

  3. 同步代碼塊

同步實例方法

❌ 錯誤示例 - 未同步的示例

@NotThreadSafepublic class SynchronizedDemo01 implements Runnable {    static int i = 0;    public void increase() {
        i++;
    }    @Override
    public void run() {        for (int j = 0; j < 100000; j++) {
            increase();
        }
    }    public static void main(String[] args) throws InterruptedException {        SynchronizedDemo01 instance = new SynchronizedDemo01();        Thread t1 = new Thread(instance);        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();        System.out.println(i);
    }
}// 輸出結果: 小於 200000 的隨機數字

Java 實例方法同步是同步在擁有該方法的對象上。這樣,每個實例其方法同步都同步在不同的對象上,即該方法所屬的實例。只有一個線程能夠在實例方法同步塊中運行。如果有多個實例存在,那麼一個線程一次可以在一個實例同步塊中執行操作。一個實例一個線程。

@ThreadSafepublic class SynchronizedDemo02 implements Runnable {    static int i = 0;    public synchronized void increase() {
        i++;
    }    @Override
    public void run() {        for (int j = 0; j < 100000; j++) {
            increase();
        }
    }    public static void main(String[] args) throws InterruptedException {        SynchronizedDemo02 instance = new SynchronizedDemo02();        Thread t1 = new Thread(instance);        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();        System.out.println(i);
    }
}// 輸出結果:// 2000000

同步靜態方法

靜態方法的同步是指同步在該方法所在的類對象上。因爲在 JVM 中一個類只能對應一個類對象,所以同時只允許一個線程執行同一個類中的靜態同步方法。

對於不同類中的靜態同步方法,一個線程可以執行每個類中的靜態同步方法而無需等待。不管類中的那個靜態同步方法被調用,一個類只能由一個線程同時執行。

@ThreadSafepublic class SynchronizedDemo03 implements Runnable {    static int i = 0;    public static synchronized void increase() {
        i++;
    }    @Override
    public void run() {        for (int j = 0; j < 100000; j++) {
            increase();
        }
    }    public static void main(String[] args) throws InterruptedException {        Thread t1 = new Thread(new SynchronizedDemo03());        Thread t2 = new Thread(new SynchronizedDemo03());
        t1.start();
        t2.start();
        t1.join();
        t2.join();        System.out.println(i);
    }
}// 輸出結果:// 200000

同步代碼塊

有時你不需要同步整個方法,而是同步方法中的一部分。Java 可以對方法的一部分進行同步。

注意 Java 同步塊構造器用括號將對象括起來。在上例中,使用了 this,即爲調用 add 方法的實例本身。在同步構造器中用括號括起來的對象叫做監視器對象。上述代碼使用監視器對象同步,同步實例方法使用調用方法本身的實例作爲監視器對象。

一次只有一個線程能夠在同步於同一個監視器對象的 Java 方法內執行。

@ThreadSafepublic class SynchronizedDemo04 implements Runnable {    static int i = 0;    static SynchronizedDemo04 instance = new SynchronizedDemo04();    @Override
    public void run() {        synchronized (instance) {            for (int j = 0; j < 100000; j++) {
                i++;
            }
        }
    }    public static void main(String[] args) throws InterruptedException {        Thread t1 = new Thread(instance);        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();        System.out.println(i);
    }
}// 輸出結果:// 200000

synchronized 的原理

synchronized 實現同步的基礎是:Java 中的每一個對象都可以作爲鎖。

  • 對於普通同步方法,鎖是當前實例對象。

  • 對於靜態同步方法,鎖是當前類的 Class 對象。

  • 對於同步方法塊,鎖是 Synchonized 括號裏配置的對象。

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