--- title: 'volatile 修飾符、自定義鎖、同步器' disqus: kyleAlien --- volatile 修飾符、自定義鎖、同步器 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**深入解析 Java 併發編程:從 3 大特性到 AbstractQueuedSynchronizer 的應用 | Volatile**](https://devtechascendancy.com/atomic-visibility-ordering-volatile-aqs/) ::: [TOC] ## 併發編程有三特性 **併發編程有三特性**:**原子性**、**可見性**、**有序性**,這三個特性都與併發編程有關戲,接下來我們分別來說明這三個特性 ### 原子、可見、有序性 * **原子性** : **不可切割的最小單位**,只有全部執行、全部不執行 何為 [**原子**](https://zh.wikipedia.org/wiki/原子) 呢? 原子是元素能保持其化學性質的最小單位,將這個概念用在程式上後,我們可以這樣說 * 如 `x = 1;` 就是原子操作 * `x = y;` 則不是,它有兩個動作,它要取出 `y`,再將 `y` 數值寫入 `x` * `x++;` 要取出 `x`,再加一,最後將數值寫入 x :::info * Java 中也有提供原子相關的類別這個類別在 `java.util.concurrent.atomic` 包中 `atomic` 使用了 **高效的機械指令來保證原子操做** > 包含了 `AtomicInteger`、`AtomicBoolean`、`AtomicLong`、`AtomicLong`、`AtomicReference` 這些原子類別 ::: * **可見性**:**一個執行序對一個數據的修改,==另一個執行序是否馬上可見==** **修飾語 `volatile` 保證了,修改原子數據後馬上存入主內存**,其他執行序讀取該值時也是從主內存讀取,而普通變數沒有被馬上存入內存 (系統決定何時存入) * **有序性**:**Java 內存模型中允許編譯器和處理器對指令`重新排序`** 重新排序對單執行序沒有影響執行的正確性,但多執行序就會影響其正確性,這時 volatile 可保證其正確性,**==volatile 禁制指令重排==** ## volatile 修飾符 * 另外 Java 還提供 volatile 這個關鍵字來保證 **可見性、有序性** * **volatile 的有序性** 在關鍵字 `volatile` 前的指令都做完了,才執行 `volatile` 修飾的指令,並且 **禁止 CPU 對 JVM 優化指令順序** * **volatile 的可見性、原子性** **`volatile` 的可見性只針對有原子特性的指令、操作有作用**,也就是說 **volatile 並不保證原子性**! * 下面示範一個雖然用了 volatile 關鍵字,但是確沒有配合「**沒有原子特性**」的操作,導致的問題範例 ```java= 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 不保證非原子性操作**!! > ![](https://i.imgur.com/Dp5V3Ty.png) :::warning * **如果使用同步(`synchronized`)就可以保證其原子性,但這種操作可能導致速度變慢** > ![](https://i.imgur.com/NMy6VGq.png) ::: * 下面示範一個「**有原子特性**」的操作 ```java= 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 關閉開關 > > ![](https://i.imgur.com/eL5UcRz.png) ### volatile 實現原理、使用時機 * **volatile 實現原理** **volatile 關鍵字修飾的變量,會存在一個 ==lock:== 的前綴** :::info lock 會對「**CPU 總線**」和「**高速緩存 Cache**」加鎖,可以理解為 CPU 指令集的鎖 ::: 同時該指令 (`volatile`) 會將當前處理器緩存的數據直接會寫到記憶體中,這個 **++寫回記憶體中的動作會使其他執行序對於該變數引用失效++**(保證了可見性) * **volatile 的同步簡單,但是脆弱**,需要小心使用,我們可以使用在: * **對變量的操作不依賴當前的值(原子元素)**,可使用在設定 State、Flag… 等等行為 * 變量只作用在一個方法中;像是單例模式中的 DCL 設計就有使用到 `volatile` 來保證變數的「**有序性**」 ## [AbstractQueuedSynchronizer](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r1:libcore/ojluni/annotations/hiddenapi/java/util/concurrent/locks/ReentrantLock.java) 概述 * 隊列同步器 AbstractQueuedSynchronizer,它是個抽象類,檔案在 `java.util.concurrent` 中,**用來==建構自定義鎖==** 或其他同步組件的基礎框架,==**它使用 `int` 成員變量表示同步狀態**== 它會通過內置的 [**FIFO 佇列**](https://hackmd.io/oE5XHO3NThCb1Grn7foPMA#Queue-%E4%BD%87%E5%88%97) 來完成資源獲取工作 > 官方網站提供的 [**ReentrantLock**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:libcore/ojluni/src/main/java/java/util/concurrent/locks/ReentrantLock.java) 內部就有實現一個 Sync 內部類,這個類就是繼承於 `AbstractQueuedSynchronizer` 類 > > ![image](https://hackmd.io/_uploads/HyoM6i4V0.png) ### 常用方法:認識獨占鎖 & 共享鎖 * **AbstractQueuedSynchronizer 內部的重要(public)常用的方法如下表** | public 方法 | 返回 | 功能 | | -------- | -------- | -------- | | `acquire(int arg)` | void | **++獨占式++獲取同步狀態**,如果沒獲取到鎖則排入隊列中等待取鎖 | | `acquireInterruptibly(int arg)` | void | **++獨占式++獲取同步狀態**,該方法**響應中斷**,如果**遇到中斷信號則會拋出 InterruptedException 並返回** | | `tryAcquireNanos(int arg, long nanosTimeout)` | **boolean** | nanos 時間內嘗試獲取++獨占鎖++,**這個方法也響應中斷** | | `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 集合 | :::info * **獨占鎖 & 共享鎖** 適當的使用獨占鎖、共享鎖可以有效的加強同步時的使用效能 * **獨占鎖主要是一個物件拿到鎖後,其他物件就不能拿鎖**,適合用在「`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 內部是使用 [**模板模式**](https://devtechascendancy.com/object-oriented_design_template/) 設計,要使用的話主要是依靠繼承,子類透過繼承 `AbstractQueuedSynchronizer` 並實現它的抽象方法,最後透過模板方法調用子類實現的抽象方法 ```java= // 該方法是空實現,必須要透過使用者自己去 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` 來管理內部同步的狀態** > ![image](https://hackmd.io/_uploads/SydRUhVE0.png) 在使用時方面免不了要對 `state` 這個屬性做修改,這時就要使用 `AbstractQueuedSynchronizer` 的三種方法 1. `getState()`:**獲取狀態** 2. `setState(int newState)`:**設定新狀態** 3. `compareAndState(int expect, int update)` : expect 及為舊的狀態,update 為新的狀態,返回 boolean 值… 這個方法很明顯的就是使用了 [**CAS 機制**](https://hackmd.io/7Ru0TE45Tnm1LEUqx4qe-A#CAS-%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C) > ![image](https://hackmd.io/_uploads/r1P9PnVEA.png) ### 內部設計 CLH 隊列鎖:公平鎖 * CLH 隊列鎖全名為 `Craig Landin and Hagersten locks`,AbstractQueuedSynchronizer 內部的一種執行序同步排序設計,**它是一種基於鏈表 Linked 資料結構所設計的可拓展、高性能的==公平鎖==** * **`Craig Landin` 鎖**: 這是一種特定的鎖定機制,通常涉及某種形式的「**自旋鎖**」,這意味著當一個處理器在等待鎖時,它會反覆檢查鎖的狀態,而不是進行上下文切換。 * **`Hagersten` 鎖**: 這也是一種「**自旋鎖定機制**」,通常與多處理器系統中的資源管理和同步有關。這類鎖定機制旨在有效地管理並行訪問,以提高系統的整體性能。 > 它的概念是使用一個 Node 內部包裝 3 個訊息,當前執行序、下一個隊列的指標、當前鎖的狀態,而每個執行序都在不算判斷前一個節點鎖的狀態,當目前的得到鎖的執行序要釋放鎖時會將狀態切到 false,隊列後方就可以獲取到鎖 :::warning Craig Landin 和 Hagersten 鎖是並行計算和多處理器系統中使用的一些同步機制的名稱,這些術語來自計算機科學領域,尤其是涉及多處理器系統和同步問題的研究 這些名字指的是特定類型的鎖或鎖定協議,用於管理多處理器系統中的資源訪問,以防止競爭條件和其他並行問題 ::: * **Node 內部的普通變數都是使用 `volatile` 關鍵字修飾**,在 Java 中使用 `volatile` 來修飾變數是用來表達該屬性的修改對於每個執行序都是可見的操作 ```java= // 當執行序出問題 (停止、中斷...) 則使用這個表達執行序狀態 volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; ``` | waitStatus 狀態 | 代號 | 發生狀況 | | -------- | -------- | -------- | | - | 0 | 初始狀態即為 0,代表沒有任何狀況都正常 | | `CANCELLED` | 1 | 由於超時 or 中斷,該節點被取消 | | `SINGNAL` | -1 | 當後面的節點被終止,避免搶後繼節點,使用 CAS 獲取 | | `CONDITION` | -2 | 該節點在條件隊列中,不使用同步節點 | | `PROPAGATE` | -3 | 在 doReleaseShared 方法 | 1. 創建 Node 儲存資料 2. 使用 `getAndSet` 方法串接上隊列 3. 執行序判斷上一個連接的執行序的 locked 狀態 4. `locked = false` 代表已經處理完畢,可換成自己的任務 > ![](https://i.imgur.com/3IyKfwJ.png) :::info Thread 在判斷上一個 lock 狀態時並不會一直判斷,大概判斷個 2~3 次,否則對於 CPU 來說負擔過重 ::: ### 繼承 AbstractQueuedSynchronizer 簡單實現:不可重入鎖 * **接下來我們使用的設計方式如下**: * **靜態內部類**(`inner static class`)來繼承 AbstractQueuedSynchronizer 類來實現資源同步器,也就是在該類中操作 CAS 機制,這裡我們就可以來實現不可重入鎖、可重入鎖 * **外部類**(`class`)使用實現自定義鎖,自定義鎖可以對外提供給使用者 > 這樣的設計可以很好的作到隔離封裝的效果 (不讓使用者直接控制) ```java= // 設計的概念程式如下 // 自定義鎖 public class MyHandlerLock { // 同步器 private static class MySynch extends AbstractQueueSynchronizer { ... } } ``` * **實現不可重入鎖** 1. **靜態內部類(`static class`) 實現同步器**:這裡的重點是在 `tryAcquire` 方法中使用 CAS 成功改變狀態後,透過 `setExclusiveOwnerThread` 方法,將前執行序設置為鎖的持有者,之後就「**直接返回**」這樣可以讓這個鎖擁有不可再重入性!! :::info * `setExclusiveOwnerThread(Thread.currentThread())` 設置了當前執行序為鎖的持有者(**這一操作本身並不能說明鎖是否可重入**) ::: 其他說明請看註解 ```java= 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 提供的一個內部類 } } ``` 2. **外部類(`class`)實現鎖機制**:實現 `Lock` 界面的方法,並且內部使用的同步器是上個步驟定義的同步器 ```java= 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; } } ``` :::spoiler 完整不可重入鎖的程式 ```java= // 手寫不可重入鎖 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; } } ``` ::: * **測試自定義不可重入鎖的程式如下** 要測試不可重入鎖最直接的方式就是使用一個鎖,來執行遞歸的函數,如果是不可重入鎖,則當前的執行序就會被卡住,導致無法正常遞歸執行 ```java= // 測試不可重入鎖 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(); } } } ``` **--實做結果--** > 從下圖結果中,我們也可以看得出來該鎖不可再被重入(卡死在地歸的第二次操作) > > ![image](https://hackmd.io/_uploads/rJ2WsNkH0.png) ### 繼承 AbstractQueuedSynchronizer 簡單實現:可重入鎖 * 依照上個小節的設計方案,這次我們來 **實現可重入鎖** 1. **靜態內部類(`static class`) 實現同步器**:可重入鎖的操作重點仍是在 `tryAcquire` 方法,在該方法中會需要判斷兩種狀態 * 在正常的透過 CAS 鎖定後,就同樣設定 `setExclusiveOwnerThread` 方法調整持有鎖的執行序為當前執行序 * 第二的狀況則是「可重入的重點」:就算已經鎖定了,**只要是同個執行序就要可重入,而這時就要去設定 State 的數值** ```java= 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(); } } ``` 2. **外部類(`class`)實現鎖機制**:實現 `Lock` 界面的方法,並且內部使用的同步器是上個步驟定義的同步器 ```java= // 創建可重入鎖 (相同執行序才可以進入) 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; } } ``` :::spoiler 完整的可重入鎖程式 ```java= // 創建可重入鎖 (相同執行序才可以進入) 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; } } ``` ::: * **測試自定義可重入鎖的程式如下** 這裡我們同樣做遞歸的程式,但稍微複雜一點~ 我們使用多執行序配合遞歸程式 ```java= // 使用 可重入鎖 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); } } } ``` **--實做--** > 開三個執行序,並使用遞歸測試 > > ![](https://i.imgur.com/V61Rmxq.png) :::spoiler 詳細輸出結果 ```shell= 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 結論 :::success 1. 使用了**模板設計** 2. 內部使用了 **CAS 機制** 3. 使用隊列 CLH,並且實現了**公平鎖** :::