深入解析 Java 併發編程:從 3 大特性到 AbstractQueuedSynchronizer 的應用 | Volatile

深入解析 Java 併發編程:從 3 大特性到 AbstractQueuedSynchronizer 的應用 | Volatile

Overview of Content

併發編程是現代軟體開發中的重要技術,而在 Java 中,併發編程有著獨特的三大特性:原子性、可見性和有序性,理解這三個特性是掌握高效併發編程的基礎。首先,原子性確保操作在併發環境下不被打斷,可見性則確保一個執行序的變更對其他執行序是可見的,而有序性保證了指令的執行順序符合預期。這三者共同保障了併發程序的正確性和效率

在 Java 中,volatile 修飾符是實現可見性的重要工具。volatile 的使用原理在於禁止指令重排序,確保變量的讀寫操作對所有執行序可見(它適用於某些輕量級同步場景,例如標誌位的更新)

進一步探討併發工具,Java 提供了強大的 AbstractQueuedSynchronizer (AQS) 框架。AQS 支持多種鎖的實現,如獨占鎖和共享鎖,其核心是利用 CAS 技術操作 state 變量,確保高效和安全的同步。AQS 的內部設計還包括 CLH 隊列鎖,用於實現公平鎖,保證所有執行序按順序獲得鎖

為了深入理解 AQS 的應用,可以通過繼承 AQS 實現不同類型的鎖。例如,不可重入鎖、可重入鎖的實現展示了 AQS 的靈活性和強大功能。最終,AQS 作為併發編程中的關鍵工具,其靈活設計和高性能使其成為開發者必備的技術。

透過本文的詳細介紹,讀者將全面了解併發編程的核心概念及其在 Java 中的具體應用,幫助您在開發高效、可靠的併發程序中取得成功

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀

個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看


併發編程有三特性

併發編程有三特性原子性可見性有序性,這三個特性都與併發編程有關戲,接下來我們分別來說明這三個特性

原子、可見、有序性

原子性 : 不可切割的最小單位,只有全部執行、全部不執行

何為 原子 呢? 原子是元素能保持其化學性質的最小單位,將這個概念用在程式上後,我們可以這樣說

● 如 x = 1; 就是原子操作

x = y; 則不是,它有兩個動作,它要取出 y,再將 y 數值寫入 x

x; 要取出 x,再加一,最後將數值寫入 x

● Java 中也有提供原子相關的類別這個類別在 java.util.concurrent.atomic 包中

atomic 使用了 高效的機械指令來保證原子操做

包含了 AtomicIntegerAtomicBooleanAtomicLongAtomicLongAtomicReference 這些原子類別

可見性一個執行序對一個數據的修改,另一個執行序是否馬上可見

修飾語 volatile 保證了,修改原子數據後馬上存入主內存,其他執行序讀取該值時也是從主內存讀取,而普通變數沒有被馬上存入內存 (系統決定何時存入)

有序性Java 內存模型中允許編譯器和處理器對指令重新排序

重新排序對單執行序沒有影響執行的正確性,但多執行序就會影響其正確性,這時 volatile 可保證其正確性,volatile 禁制指令重排


volatile 修飾符

● 另外 Java 還提供 volatile 這個關鍵字來保證 可見性、有序性

volatile 的有序性

在關鍵字 volatile 前的指令都做完了,才執行 volatile 修飾的指令,並且 禁止 CPU 對 JVM 優化指令順序

volatile 的可見性、原子性

volatile 的可見性只針對有原子特性的指令、操作有作用,也就是說 volatile 並不保證原子性

● 下面示範一個雖然用了 volatile 關鍵字,但是確沒有配合「沒有原子特性」的操作,導致的問題範例


public class TestVolatile {
    private volatile int a = 0;

    public void add() {
        a++;            // 這是一個沒有原子性的操作!!!
    }

    public static void main(String[] args) {
        TestVolatile v = new TestVolatile();

        for(int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    for(int j = 0; j < 1000; j++) {
                        v.add();
                    }
                }
            }.start();
        }

        while(Thread.activeCount() > 2) {
            Thread.yield();		// When have work thread, yield the resource
        }

        System.out.println("a is " + v.a);
    }

}

--實作--

使用了 valatile 修飾變數 a,並呼叫 10 個執行序,每個執行 a++ 1000次,預期結果應該是 10000,但是不是,代表了 valatile 不保證非原子性操作!!

如果使用同步(synchronized)就可以保證其原子性,但這種操作可能導致速度變慢

● 下面示範一個「有原子特性」的操作


public class TestVolatile {

    public static void main(String[] args) {
        TestClass v = new TestClass();

        new Thread(v).start();

        try {
            TimeUnit.MICROSECONDS.sleep(1);
            new Thread(v).start();
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        v.setStop();

        System.out.println("a is " + v.getValue());
    }
}

class TestClass implements Runnable {

    private volatile boolean stop = false;
    private int a = 0;

    public void setStop() {
        stop = true;            // 這種設定方式,就是一種原子操作
    }

    @Override
    public void run() {
        while(!stop) {
            System.out.println(Thread.currentThread().getName() 
                    + " real value : " + a);
            synchronized(this) {
                a++;
                try {
                    TimeUnit.MICROSECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public int getValue() {
        return a;
    }
}

--實作--

Thread 0 馬上啟動,Thread 1 延遲 1ms 啟動,1秒後 Thread 0 關閉開關

volatile 實現原理、使用時機

volatile 實現原理

volatile 關鍵字修飾的變量,會存在一個 lock: 的前綴

lock 會對「CPU 總線」和「高速緩存 Cache」加鎖,可以理解為 CPU 指令集的鎖

同時該指令 (volatile) 會將當前處理器緩存的數據直接會寫到記憶體中,這個 寫回記憶體中的動作會使其他執行序對於該變數引用失效(保證了可見性)

volatile 的同步簡單,但是脆弱,需要小心使用,我們可以使用在:

對變量的操作不依賴當前的值(原子元素),可使用在設定 State、Flag… 等等行為

● 變量只作用在一個方法中;像是單例模式中的 DCL 設計就有使用到 volatile 來保證變數的「有序性


AbstractQueuedSynchronizer 框架概述

● 隊列同步器 AbstractQueuedSynchronizer,它是個抽象類,檔案在 java.util.concurrent 中,用來建構自定義鎖 或其他同步組件的基礎框架,它使用 int 成員變量表示同步狀態

它會通過內置的 FIFO 佇列 來完成資源獲取工作

官方網站提供的 ReentrantLock 內部就有實現一個 Sync 內部類,這個類就是繼承於 AbstractQueuedSynchronizer

常用方法:認識獨占鎖 & 共享鎖

AbstractQueuedSynchronizer 內部的重要(public)常用的方法如下表

public 方法返回功能
acquire(int arg)void++獨占式++獲取同步狀態,如果沒獲取到鎖則排入隊列中等待取鎖
acquireInterruptibly(int arg)void++獨占式++獲取同步狀態,該方法響應中斷,如果遇到中斷信號則會拋出 InterruptedException 並返回
tryAcquireNanos(int arg, long nanosTimeout)booleannanos 時間內嘗試獲取++獨占鎖++,這個方法也響應中斷
acquireShared(int arg)void獲取++共享鎖++,但是不響應中斷
acquireSharedInterruptibly(int arg)void響應中斷的共享鎖
tryAcquireSharedNanos(int arg, long nanosTimeout)boolean時間內獲取共享鎖,響應中斷
release(int arg)boolean釋放獨占鎖
releaseShared(int arg)boolean釋放共享鎖
getExclusiveQueuedThreads()Collection<Thread>獲取在等待獨占鎖的 thread 集合

獨占鎖 & 共享鎖

適當的使用獨占鎖、共享鎖可以有效的加強同步時的使用效能

獨占鎖主要是一個物件拿到鎖後,其他物件就不能拿鎖,適合用在「Write」資訊

共享鎖與之相反,可以有多個物件拿到同一把鎖,適合用在「Read」時

AbstractQueuedSynchronizer 中可被重寫的(protected)方法 (內部是空實現,會拋出 UnsupportedOperationException 異常)

protected 方法返回功能
tryAcquire(int arg)boolean嘗試獲取獨占鎖,傳入的參數為舊值(預期值)
tryAcquireShared(int arg)int嘗試獲取共享鎖,如果返回值 > 0 代表獲取成功
tryRelease(int arg)boolean嘗試釋放獨占鎖
tryReleaseShared(int arg)boolean嘗試釋放共享鎖
isHeldExclusively()boolean當前的同步器(繼承 AQS 的類),是否在獨占模式下被當前執行序佔用 (calling thread)

自訂鎖需要的方法

方法返回功能
setExclusiveOwnerThread()boolean設定目前所以訪問的執行序,該方法於 AQS 的父類

內部設計模板:CAS 技術操作 state 變量

● AbstractQueuedSynchronizer 內部是使用 模板模式 設計,要使用的話主要是依靠繼承,子類透過繼承 AbstractQueuedSynchronizer 並實現它的抽象方法,最後透過模板方法調用子類實現的抽象方法


// 該方法是空實現,必須要透過使用者自己去 Override
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 以請求加入隊列來說,它使用了 tryAcquire 方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

● AbstractQueuedSynchronizer 內部是使用了 int state 來管理內部同步的狀態

在使用時方面免不了要對 state 這個屬性做修改,這時就要使用 AbstractQueuedSynchronizer 的三種方法

A. getState()獲取狀態

B. setState(int newState)設定新狀態

C. compareAndState(int expect, int update) : expect 及為舊的狀態,update 為新的狀態,返回 boolean 值… 這個方法很明顯的就是使用了 CAS 機制

內部設計 CLH 隊列鎖:公平鎖

● CLH 隊列鎖全名為 Craig Landin and Hagersten locks,AbstractQueuedSynchronizer 內部的一種執行序同步排序設計,它是一種基於鏈表 Linked 資料結構所設計的可拓展、高性能的公平鎖

Craig Landin

這是一種特定的鎖定機制,通常涉及某種形式的「自旋鎖」,這意味著當一個處理器在等待鎖時,它會反覆檢查鎖的狀態,而不是進行上下文切換。

Hagersten

這也是一種「自旋鎖定機制」,通常與多處理器系統中的資源管理和同步有關。這類鎖定機制旨在有效地管理並行訪問,以提高系統的整體性能。

它的概念是使用一個 Node 內部包裝 3 個訊息,當前執行序、下一個隊列的指標、當前鎖的狀態,而每個執行序都在不算判斷前一個節點鎖的狀態,當目前的得到鎖的執行序要釋放鎖時會將狀態切到 false,隊列後方就可以獲取到鎖

Craig LandinHagersten 鎖是並行計算和多處理器系統中使用的一些同步機制的名稱,這些術語來自計算機科學領域,尤其是涉及多處理器系統和同步問題的研究

這些名字指的是特定類型的鎖或鎖定協議,用於管理多處理器系統中的資源訪問,以防止競爭條件和其他並行問題

Node 內部的普通變數都是使用 volatile 關鍵字修飾,在 Java 中使用 volatile 來修飾變數是用來表達該屬性的修改對於每個執行序都是可見的操作


// 當執行序出問題 (停止、中斷...) 則使用這個表達執行序狀態
volatile int waitStatus;

volatile Node prev;

volatile Node next;

volatile Thread thread;
waitStatus 狀態代號發生狀況
-0初始狀態即為 0,代表沒有任何狀況都正常
CANCELLED1由於超時 or 中斷,該節點被取消
SINGNAL-1當後面的節點被終止,避免搶後繼節點,使用 CAS 獲取
CONDITION-2該節點在條件隊列中,不使用同步節點
PROPAGATE-3在 doReleaseShared 方法

A. 創建 Node 儲存資料

B. 使用 getAndSet 方法串接上隊列

C. 執行序判斷上一個連接的執行序的 locked 狀態

D. locked = false 代表已經處理完畢,可換成自己的任務

Thread 在判斷上一個 lock 狀態時並不會一直判斷,大概判斷個 2~3 次,否則對於 CPU 來說負擔過重

繼承 AbstractQueuedSynchronizer 簡單實現:不可重入鎖

接下來我們使用的設計方式如下

靜態內部類inner static class)來繼承 AbstractQueuedSynchronizer 類來實現資源同步器,也就是在該類中操作 CAS 機制,這裡我們就可以來實現不可重入鎖、可重入鎖

外部類class)使用實現自定義鎖,自定義鎖可以對外提供給使用者

這樣的設計可以很好的作到隔離封裝的效果 (不讓使用者直接控制)


// 設計的概念程式如下

// 自定義鎖
public class MyHandlerLock {

    // 同步器
    private static class MySynch extends AbstractQueueSynchronizer {
        ...
    }
}

實現不可重入鎖

A. 靜態內部類(static class) 實現同步器:這裡的重點是在 tryAcquire 方法中使用 CAS 成功改變狀態後,透過 setExclusiveOwnerThread 方法,將前執行序設置為鎖的持有者,之後就「直接返回」這樣可以讓這個鎖擁有不可再重入性!!

setExclusiveOwnerThread(Thread.currentThread()) 設置了當前執行序為鎖的持有者(這一操作本身並不能說明鎖是否可重入

其他說明請看註解


private static class MySync extends AbstractQueuedSynchronizer {

    private static final long serialVersionUID = -6564653263134789293L;
    private static final int UNLOCKED = 0;
    private static final int LOCKED = 1;

    // 鎖是否已被持有
    @Override
    protected boolean isHeldExclusively() {
        // 返回同步狀態的值,該值使用 volatile 修飾
        // 自己設定為 1 時為鎖住的狀態
        return getState() == LOCKED;
    }

    // 獲取鎖
    @Override
    protected boolean tryAcquire(int arg) {
        // 使用 CAS 的方法 compareAndSetState,成功後返回 true
        // 轉為鎖定狀態(UNLOCKED -> LOCKED)
        if(compareAndSetState(UNLOCKED, LOCKED)) {
            // 鎖定成功後,設置當前執行序,是鎖的程有者
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    // 釋放鎖
    @Override
    protected boolean tryRelease(int arg) {
        if(getState() == UNLOCKED) {
            // 未鎖定狀態不需要再釋放
            throw new IllegalMonitorStateException();
        }

        if(compareAndSetState(LOCKED, UNLOCKED)) {
            // null 表示沒有任何執行序持有該鎖
            setExclusiveOwnerThread(null);
            return true;
        }
        return false;
    }

    // Condition 為一個接口
    Condition newCondition() {
        /**
         * 會 new 出一個 狀態為 condition 的 Node,並切換到隊列的頭節點
         */
        return new ConditionObject();	// AQS 提供的一個內部類
    }

}

B. 外部類(class)實現鎖機制:實現 Lock 界面的方法,並且內部使用的同步器是上個步驟定義的同步器


public class OccupyHandlerLock implements Lock {

    // 使用 內部類實現的同步器
    private MySync mySync = new MySync();

    @Override
    public void lock() {
        System.out.println(Thread.currentThread().getName() + " Ready Get Lock");
        mySync.acquire(MySync.LOCKED);
        System.out.println(Thread.currentThread().getName() + " Get Lock !!!");
    }

    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + " Ready Release Lock");
        mySync.release(MySync.UNLOCKED);
        System.out.println(Thread.currentThread().getName() + " Release Lock !!!");
    }

    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(MySync.LOCKED);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 未實現
        return mySync.tryAcquireNanos(MySync.LOCKED, unit.toNanos(time));
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // 該方法會對當前線程發出中斷信號,並且把參數發置 tryAcquire
        mySync.acquireInterruptibly(MySync.LOCKED);
    }

    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }

    public boolean isLocked() {
        return mySync.isHeldExclusively();
    }

    public boolean isQueueHasTask() {
        return mySync.getQueueLength() > 0;
    }

}
完整不可重入鎖的程式

// 手寫不可重入鎖

public class OccupyHandlerLock implements Lock {

    private static class MySync extends AbstractQueuedSynchronizer {

        private static final long serialVersionUID = -6564653263134789293L;
        private static final int UNLOCKED = 0;
        private static final int LOCKED = 1;

        // 鎖是否已被持有
        @Override
        protected boolean isHeldExclusively() {
            // 返回同步狀態的值,該值使用 volatile 修飾
            return getState() == LOCKED;		// 自己設定為 1 時為鎖住的狀態
        }

        // 獲取鎖
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(UNLOCKED, LOCKED)) {
                // 設置排他的執行序
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 釋放鎖
        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == UNLOCKED) {
                throw new IllegalMonitorStateException();
            }

            if(compareAndSetState(LOCKED, UNLOCKED)) {
                // null 表示沒有任何執行序持有該鎖
                setExclusiveOwnerThread(null);
                return true;
            }
            return false;
        }

        // Condition 為一個接口
        Condition newCondition() {
            /**
             * 會 new 出一個 狀態為 condition 的 Node,並切換到隊列的頭節點
             */
            return new ConditionObject();	// AQS 提供的一個內部類
        }

    }

    // -----------------------------------------------------------------

    private MySync mySync = new MySync();

    @Override
    public void lock() {
        System.out.println(Thread.currentThread().getName() + " Ready Get Lock");
        mySync.acquire(MySync.LOCKED);
        System.out.println(Thread.currentThread().getName() + " Get Lock !!!");
    }

    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + " Ready Release Lock");
        mySync.release(MySync.UNLOCKED);
        System.out.println(Thread.currentThread().getName() + " Release Lock !!!");
    }

    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(MySync.LOCKED);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 未實現
        return mySync.tryAcquireNanos(MySync.LOCKED, unit.toNanos(time));
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // 該方法會對當前線程發出中斷信號,並且把參數發置 tryAcquire
        mySync.acquireInterruptibly(MySync.LOCKED);
    }

    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }

    public boolean isLocked() {
        return mySync.isHeldExclusively();
    }

    public boolean isQueueHasTask() {
        return mySync.getQueueLength() > 0;
    }

}

測試自定義不可重入鎖的程式如下

要測試不可重入鎖最直接的方式就是使用一個鎖,來執行遞歸的函數,如果是不可重入鎖,則當前的執行序就會被卡住,導致無法正常遞歸執行


// 測試不可重入鎖

public class TESTOccupy {

    private static Lock lock = new OccupyHandlerLock();

    public static void main(String[] args) {

        testFunction(10);

    }

    static void testFunction(int value) {
        try {
            lock.lock();

            if (value == 0) return;

            System.out.println("Current value: " + value);

            testFunction(value - 1);

        } finally {
            lock.unlock();
        }

    }

}

--實做結果--

從下圖結果中,我們也可以看得出來該鎖不可再被重入(卡死在地歸的第二次操作)

繼承 AbstractQueuedSynchronizer 簡單實現:可重入鎖

● 依照上個小節的設計方案,這次我們來 實現可重入鎖

A. 靜態內部類(static class) 實現同步器:可重入鎖的操作重點仍是在 tryAcquire 方法,在該方法中會需要判斷兩種狀態

● 在正常的透過 CAS 鎖定後,就同樣設定 setExclusiveOwnerThread 方法調整持有鎖的執行序為當前執行序

● 第二的狀況則是「可重入的重點」:就算已經鎖定了,只要是同個執行序就要可重入,而這時就要去設定 State 的數值


private static class MySync_2 extends AbstractQueuedSynchronizer {

    private static final long serialVersionUID = -8298045247459685714L;
    private static final int UNLOCKED = 0;
    private static final int LOCKED = 1;

    @Override
    protected boolean isHeldExclusively() {
        return getState() > 0;	// 重入超過一個就算是以鎖住
    }

    @Override
    protected boolean tryAcquire(int arg) {
        //Thread now = Thread.currentThread();
        if(compareAndSetState(UNLOCKED, LOCKED)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;

        } else if(getExclusiveOwnerThread() == Thread.currentThread()) {	// 只有同線程 才可重入
            setState(getState() + 1); // 重點 !!! 仍然是 0 才是釋放
            return true;

        }
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        // 如果不是當前的執行序就「不釋放」
        if(getExclusiveOwnerThread() != Thread.currentThread()) {
            throw new IllegalMonitorStateException();
        }

        if (getState() == UNLOCKED) {	// 已沒有鎖住
            throw new IllegalMonitorStateException();
        }

        setState(getState() - 1);
        if(getState() == 0) {
            setExclusiveOwnerThread(null);
            System.out.println("Release Finish");
        } else {
            System.out.println("Release Havn't Done: " + getState());
        }

        return true;
    }

    Condition newCondition() {
        return new ConditionObject();
    }
}

B. 外部類(class)實現鎖機制:實現 Lock 界面的方法,並且內部使用的同步器是上個步驟定義的同步器


// 創建可重入鎖 (相同執行序才可以進入)

public class SharedHandlerLock implements Lock {

    private MySync_2 mySync = new MySync_2();

    @Override
    public void lock() {
        System.out.println(Thread.currentThread().getName() + " Ready Get Lock");
        mySync.acquire(MySync_2.LOCKED);
        System.out.println(Thread.currentThread().getName() + " Get Lock !!!");
    }

    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + " Ready Release Lock");
        mySync.release(MySync_2.UNLOCKED);
        System.out.println(Thread.currentThread().getName() + " Release Lock !!!");
    }

    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(MySync_2.LOCKED);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 未實現
        return mySync.tryAcquireNanos(MySync_2.LOCKED, unit.toNanos(time));
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // 該方法會對當前線程發出中斷信號,並且把參數發置 tryAcquire
        mySync.acquireInterruptibly(MySync_2.LOCKED);
    }

    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }

    public boolean isLocked() {
        return mySync.isHeldExclusively();
    }

    public boolean isQueueHasTask() {
        return mySync.getQueueLength() > 0;
    }
}

完整的可重入鎖程式

// 創建可重入鎖 (相同執行序才可以進入)
public class SharedHandlerLock implements Lock {

    private static class MySync_2 extends AbstractQueuedSynchronizer {

        private static final long serialVersionUID = -8298045247459685714L;
        private static final int UNLOCKED = 0;
        private static final int LOCKED = 1;

        @Override
        protected boolean isHeldExclusively() {
            return getState() > 0;	// 重入超過一個就算是以鎖住
        }

        @Override
        protected boolean tryAcquire(int arg) {
            //Thread now = Thread.currentThread();
            if(compareAndSetState(UNLOCKED, LOCKED)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;

            } else if(getExclusiveOwnerThread() == Thread.currentThread()) {	// 只有同線程 才可重入
                setState(getState() + 1); // 重點 !!! 仍然是 0 才是釋放
                return true;

            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            // 如果不是當前的執行序就「不釋放」
            if(getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }

            if (getState() == UNLOCKED) {	// 已沒有鎖住
                throw new IllegalMonitorStateException();
            }

            setState(getState() - 1);
            if(getState() == 0) {
                setExclusiveOwnerThread(null);
                System.out.println("Release Finish");
            } else {
                System.out.println("Release Havn't Done: " + getState());
            }

            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // -----------------------------------------------------------------
    private MySync_2 mySync = new MySync_2();

    @Override
    public void lock() {
        System.out.println(Thread.currentThread().getName() + " Ready Get Lock");
        mySync.acquire(MySync_2.LOCKED);
        System.out.println(Thread.currentThread().getName() + " Get Lock !!!");
    }

    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + " Ready Release Lock");
        mySync.release(MySync_2.UNLOCKED);
        System.out.println(Thread.currentThread().getName() + " Release Lock !!!");
    }

    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(MySync_2.LOCKED);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 未實現
        return mySync.tryAcquireNanos(MySync_2.LOCKED, unit.toNanos(time));
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // 該方法會對當前線程發出中斷信號,並且把參數發置 tryAcquire
        mySync.acquireInterruptibly(MySync_2.LOCKED);
    }

    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }

    public boolean isLocked() {
        return mySync.isHeldExclusively();
    }

    public boolean isQueueHasTask() {
        return mySync.getQueueLength() > 0;
    }
}

測試自定義可重入鎖的程式如下

這裡我們同樣做遞歸的程式,但稍微複雜一點~ 我們使用多執行序配合遞歸程式


// 使用 可重入鎖
public class TESTShared {

    public static void main(String[] args) { 
        for(int i = 0; i < 3; i++) {
            new MyTask_2().start();
        }
    }

    static class MyTask_2 extends Thread {
        // 重入鎖
        private static Lock lock = new SharedHandlerLock();

        private void reenter(int times) {
            lock.lock();
            try {
                System.out.println(getName() + " 遞歸層:" + times);
                if(--times == 0) {
                    return;
                } else {
                    reenter(times);
                }
            } finally {
                System.out.println(getName() + " Release");
                lock.unlock();
            }
        }

        @Override 
        public void run() {
            System.out.println(getName() + " Start");
            reenter(3);
        }
    }

}

--實做--

開三個執行序,並使用遞歸測試

詳細輸出結果

Thread-2 Start
Thread-1 Start
Thread-1 Ready Get Lock
Thread-0 Start
Thread-1 Get Lock !!!
Thread-2 Ready Get Lock
Thread-1 遞歸層:3
Thread-1 Ready Get Lock
Thread-0 Ready Get Lock
Thread-1 Get Lock !!!
Thread-1 遞歸層:2
Thread-1 Ready Get Lock
Thread-1 Get Lock !!!
Thread-1 遞歸層:1
Thread-1 Release
Thread-1 Ready Release Lock
Release Havn't Done: 2
Thread-1 Release Lock !!!
Thread-1 Release
Thread-1 Ready Release Lock
Release Havn't Done: 1
Thread-1 Release Lock !!!
Thread-1 Release
Thread-1 Ready Release Lock
Release Finish
Thread-1 Release Lock !!!
Thread-2 Get Lock !!!
Thread-2 遞歸層:3
Thread-2 Ready Get Lock
Thread-2 Get Lock !!!
Thread-2 遞歸層:2
Thread-2 Ready Get Lock
Thread-2 Get Lock !!!
Thread-2 遞歸層:1
Thread-2 Release
Thread-2 Ready Release Lock
Release Havn't Done: 2
Thread-2 Release Lock !!!
Thread-2 Release
Thread-2 Ready Release Lock
Release Havn't Done: 1
Thread-2 Release Lock !!!
Thread-2 Release
Thread-2 Ready Release Lock
Release Finish
Thread-2 Release Lock !!!
Thread-0 Get Lock !!!
Thread-0 遞歸層:3
Thread-0 Ready Get Lock
Thread-0 Get Lock !!!
Thread-0 遞歸層:2
Thread-0 Ready Get Lock
Thread-0 Get Lock !!!
Thread-0 遞歸層:1
Thread-0 Release
Thread-0 Ready Release Lock
Release Havn't Done: 2
Thread-0 Release Lock !!!
Thread-0 Release
Thread-0 Ready Release Lock
Release Havn't Done: 1
Thread-0 Release Lock !!!
Thread-0 Release
Thread-0 Ready Release Lock
Release Finish
Thread-0 Release Lock !!!

AbstractQueuedSynchronizer 結論

A. 使用了模板設計

B. 內部使用了 CAS 機制

C. 使用隊列 CLH,並且實現了公平鎖


更多的 Java 語言相關文章

Java 語言深入

● 在這個系列中,我們全方位地探討了 Java 語言的各個核心主題,旨在幫助你徹底掌握這門強大的編程語言。無論你是想深入理解 Java 的基礎類型與變數作用域,還是探索異常處理與運算子的細節,這些文章都將為您提供寶貴的知識

深入 Java 物件導向

● 探索 Java 物件導向的奧妙,掌握介面、抽象類、繼承等重要概念!幫助你針對物件導向設計有更深入的了解!

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響