1 Lock lock = new ReentrantLock(); //可以是自己實現的Lock接口的實現類,也可以是jdk提供的同步組件
2 lock.lock();//一般不能放到try語句中 3 try { 4 } finally { 5 lock.unlock(); //一般要求放到finally中,確保即使發生異常也能安全釋放掉鎖 6 }
- 在finally塊中釋放鎖,目的是保證在獲取到鎖之后,即使發生異常,鎖依然能被順利釋放,從而避免死鎖情況的發生。
- 不要將獲取鎖的過程寫在try塊中。假設放到try中,如果在獲取鎖時發生了異常,即鎖沒有被成功獲取到,但finally語句中有釋放鎖的操作,這就會造成死鎖,因為根本沒有獲取到鎖,而底下又要求釋放鎖。如果沒有放到try中,當獲取鎖失敗時,代碼立即會報異常而終止運行,因此就避免了死鎖。
3.相比於synchronized,Lock接口所具備的其他特性
①嘗試非阻塞的獲取鎖tryLock():當前線程嘗試獲取鎖,如果該時刻鎖沒有被其他線程獲取到,就能成功獲取並持有鎖,接着返回true,如果沒有獲取到則返回false。
②能被中斷的獲取鎖lockInterruptibly():獲取鎖的線程能夠響應中斷。當線程在獲取鎖定過程中,如果鎖被其他線程占用,則線程一直處於休眠狀態,直到獲取到鎖或被其他線程中斷才返回。要注意該線程允許其他線程調用Thread.interrupt()方法來中斷等待的線程,當線程被中斷掉,不會在去獲取鎖,會拋出interruptedException異常。
③超時的獲取鎖tryLock(long time, TimeUnit unit):在指定的截止時間獲取鎖,如果沒有獲取到鎖返回false。
CLH(Craig,Landin,and Hagersten)隊列是一個虛擬的雙向隊列,虛擬的雙向隊列即不存在隊列實例,僅存在節點之間的關聯關系。AQS是將每一條請求共享資源的線程封裝成一個CLH鎖隊列的一個結點(Node),來實現鎖的分配。
用大白話來說,AQS就是基於CLH隊列,用volatile修飾共享變量state,線程通過CAS去改變狀態符,成功則獲取鎖成功,失敗則進入等待隊列,等待被喚醒。
在AQS中維護了一個volatile int state(代表共享資源)和一個FIFO存放被阻塞的線程的同步隊列(多線程爭用資源被阻塞時會進入此隊列)。
其中state可以使用同步器提供的3個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操作,因為它們使用CAS操作能夠保證狀態的改變是安全的。
那AQS的該如何使用呢?
首先,我們需要去繼承AbstractQueuedSynchronizer這個類,然后我們根據我們的需求去重寫相應的方法,比如要實現一個獨占鎖,那就去重寫tryAcquire,tryRelease方法,要實現共享鎖,就去重寫tryAcquireShared,tryReleaseShared;最后,在我們的組件中調用AQS中的模板方法就可以了,而這些模板方法是會調用到我們之前重寫的那些方法的。也就是說,我們只需要很小的工作量就可以實現自己的同步組件,重寫的那些方法,僅僅是一些簡單的對於共享資源state的獲取和釋放操作,至於像是獲取資源失敗,線程需要阻塞之類的操作,自然是AQS幫我們完成了。
我們來看看AQS定義的這些可重寫的方法:
protected boolean tryAcquire(int arg) : 獨占式獲取同步狀態,試着獲取,成功返回true,反之為false
protected boolean tryRelease(int arg) :獨占式釋放同步狀態,等待中的其他線程此時將有機會獲取到同步狀態;
protected int tryAcquireShared(int arg) :共享式獲取同步狀態,返回值大於等於0,代表獲取成功;反之獲取失敗;
protected boolean tryReleaseShared(int arg) :共享式釋放同步狀態,成功為true,失敗為false
protected boolean isHeldExclusively() : 是否在獨占模式下被線程占用。
接下來我們舉一個自定義實現鎖的實例的代碼:
1 package juc; 2 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 3 //Mutex是我們自定的鎖 4 public class Mutex implements java.io.Serializable { 5 //靜態內部類,繼承AQS 6 private static class Sync extends AbstractQueuedSynchronizer { 7 //是否處於占用狀態 8 protected boolean isHeldExclusively() { 9 return getState() == 1; 10 } 11 //當狀態為0的時候獲取鎖,CAS操作成功,則state狀態為1, 12 public boolean tryAcquire(int acquires) { 13 if (compareAndSetState(0, 1)) { 14 setExclusiveOwnerThread(Thread.currentThread()); 15 return true; 16 } 17 return false; 18 } 19 //釋放鎖,將同步狀態置為0 20 protected boolean tryRelease(int releases) { 21 if (getState() == 0) throw new IllegalMonitorStateException(); 22 setExclusiveOwnerThread(null); 23 setState(0); 24 return true; 25 } 26 } 27 //同步對象完成一系列復雜的操作,我們僅需指向它即可 28 private final Sync sync = new Sync(); 29 //加鎖操作,代理到acquire(模板方法)上就行,acquire會調用我們重寫的tryAcquire方法 30 public void lock() { 31 sync.acquire(1); 32 } 33 public boolean tryLock() { 34 return sync.tryAcquire(1); 35 } 36 //釋放鎖,代理到release(模板方法)上就行,release會調用我們重寫的tryRelease方法。 37 public void unlock() { 38 sync.release(1); 39 } 40 public boolean isLocked() { 41 return sync.isHeldExclusively(); 42 } 43 }
上面是鎖的實現,其使用的方法和ReentrantLock的使用方法一樣,因為ReentrantLock也是基於AQS實現的。
通過前面介紹AQS的框架和使用方法,我們知道它是基於同步對列和state變量實現的,使用同步隊列來存放被阻塞的線程。接下來就是介紹它是怎樣運用同步隊列的?
static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus;//等待狀態 volatile Node prev;//指向前一個結點的指針 volatile Node next;//指向后一個節點的指針 volatile Thread thread;//當前結點代表的狀態 Node nextWaiter;
前面我們提到過,AQS維護一個共享資源state,通過內置的FIFO來完成獲取資源線程的排隊工作。(這個內置的同步隊列稱為"CLH"隊列)。該隊列由一個一個的Node結點組成,每個Node結點維護一個prev引用和next引用,分別指向自己的前驅和后繼結點。AQS維護兩個指針,分別指向隊列頭部head和尾部tail。注意隊列中的第一個元素表示正在使用鎖的線程,而隊列中第二個結點才是第一個真正排隊的結點,同步隊列的基本結構如圖所示。

其實就是個雙端雙向鏈表。
為了接下來能夠更好的理解加鎖和解鎖過程的源碼,對該同步隊列的特性進行簡單的講解:
- 1.同步隊列是個先進先出(FIFO)隊列,獲取鎖失敗的線程將構造結點並加入隊列的尾部,並阻塞自己。如何才能線程安全的實現入隊是后面講解的重點,畢竟我們在講鎖的實現,這部分代碼肯定是不能用鎖的。
- 2.隊列首結點可以用來表示當前正獲取鎖的線程。
- 3.當前線程釋放鎖后將嘗試喚醒后續處結點中處於阻塞狀態的線程。
3.AQS的底層源碼分析

之前看的這篇博客感覺寫的不錯,在這里就直接引用下:https://blog.csdn.net/java_lyvee/article/details/98966684
下面是我根據博客梳理的AQS的tryAcquire()的執行過程圖:

https://www.cnblogs.com/chengxiao/p/7141160.html
