ReentrantLock是可重入的獨佔鎖,同時只能有一個線程能夠獲取該鎖,其餘獲取該鎖的線程會被阻塞後放入該鎖的AQS阻塞隊列裏面。java
首先咱們先看一下ReentrantLock的類圖結構,以下圖所示:數組
從類圖能夠知道,ReentrantLock最終仍是使用AQS來實現,而且根據參數決定內部是公平鎖仍是非公平鎖,默認是非公平鎖。安全
首先咱們先看ReentrantLock源碼,看到其構造函數及其參數,這是決定內部是公平鎖仍是非公平鎖,以下源碼所示:函數
public ReentrantLock() { sync = new NonfairSync(); }
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
其中類Sync直接繼承自AQS,它的子類NonfairSync和FairSync分別實現了獲取鎖的公平和非公平策略。ui
在這裏AQS的狀態值state表明線程獲取該鎖的可重入次數,默認狀況下state的值爲0,標示當前鎖沒有被任何線程持有,當一個線程第一次獲取該鎖的時候會嘗試使用CAS設置state的值爲1,若是CAS成功則當前線程獲取了該鎖,而後記錄該鎖的持有者爲當前線程,spa
在該線程沒有釋放鎖,第二次獲取該鎖後,狀態值會加1,被設置爲2,這就是可重入次數,在該線程釋放該鎖的時候,會嘗試使用CAS讓狀態值減1,若是減1 後狀態值爲0 則當前線程釋放該鎖。線程
接下來咱們看一下ReentrantLock是如何獲取鎖的,以下:code
1.void lock() 當一個線程調用該方法,說明該線程但願獲取該鎖,若是鎖當前沒有被其它線程佔用而且當前線程以前沒有獲取該鎖,則當前線程會獲取到該鎖,而後設置當前鎖的擁有者爲當前線程,並設置 AQS 的狀態值爲 1 後直接返回。blog
若是當前線程以前已經獲取過該鎖,則此次只是簡單的把 AQS 的狀態值 status 加 1 後返回。 若是該鎖已經被其它線程持有,則調用該方法的線程會被放入 AQS 隊列後阻塞掛起。源碼以下:繼承
public void lock() { sync.lock(); }
如上面代碼所示,ReentrantLock的lock()是委託給sync類,根據建立ReentrantLock的時候,構造函數選擇sync的實現是NonfairSync或者FairSync,這裏先看sync的子類NonfairSync的狀況,也就是非公平鎖的時候,源碼以下:
final void lock() { //(1)CAS設置狀態值 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else //(2)調用AQS的acquire方法 acquire(1); }
如上面代碼所示,代碼(1)由於默認AQS的狀態值爲0,因此第一個調用Lock的線程會經過CAS設置狀態值爲1,CAS成功則表明當前線程獲取到了鎖,而後setExclusiveOwnerThread 設置了該鎖持有者是當前線程。
若是這時候有其餘線程調用lock方法企圖獲取該鎖,執行代碼(1)CAS會失敗,而後會調用AQS的acquire方法,這裏注意傳遞參數爲1,接下來咱們看AQS的acquire的核心代碼,以下:
public final void acquire(int arg) { //(3)調用ReentrantLock重寫的tryAcquire方法 if (!tryAcquire(arg) && // tryAcquiref返回false會把當前線程放入AQS阻塞隊列 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
以前說過 AQS 並無提供可用的 tryAcquire 方法,tryAcquire 方法須要子類本身定製化,因此這裏代碼(3)會調用 ReentrantLock 重寫的 tryAcquire 方法代碼。
這裏先看下非公平鎖的源碼代碼以下:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //(4)當前AQS狀態值爲0 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } }//(5)當前線程是該鎖持有者 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }//(6) return false; }
正如上面代碼(4)會看當前鎖的狀態值是否爲0,爲0則說明當前該鎖空閒,那麼就嘗試CAS獲取該鎖(嘗試將 AQS 的狀態值從 0 設置爲 1),並設置當前鎖的持有者爲當前線程返回返回 true。
若是當前狀態值不爲0 則說明該鎖已經被某個縣城持有,因此代碼(5)看當前線程是不是該鎖的持有者,若是當前線程是該鎖持有者,狀態值增長1,而後返回true。
若是當前線程不是鎖的持有者則返回 false, 而後會被放入 AQS 阻塞隊列。
到目前爲止,介紹完了非公平鎖的實現代碼,回過頭看看非公平鎖在這裏是怎麼體現的,首先非公平是說:先嚐試獲取鎖的線程並不必定比後嘗試獲取鎖的線程優先獲取鎖。
這裏假設線程 A 調用 lock()方法時候執行到了 nonfairTryAcquire 的代碼(4)發現當前狀態值不爲 0,因此執行代碼(5)發現當前線程不是線程持有者,則執行代碼(6)返回 false,而後當前線程會被放入了 AQS 阻塞隊列。
這時候線程 B 也調用了 lock() 方法執行到 nonfairTryAcquire 的代碼(4)時候發現當前狀態值爲 0 了(假設佔有該鎖的其它線程釋放了該鎖)因此經過 CAS 設置獲取到了該鎖。而明明是線程 A 先請求獲取的該鎖那,這就是非公平鎖的實現,
這裏線程 B 在獲取鎖前並無看當前 AQS 隊列裏面是否有比本身請求該鎖更早的線程,而是使用了搶奪策略。
好了,知道非公平鎖的實現了,那麼咱們接下來看一下公平鎖是如何實現的呢?
公平鎖的實現只須要看FairSync重寫的tryAcquire方法,源碼以下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //(7)當前AQS狀態值爲0 if (c == 0) { //(8)公平性策略 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //(9)當前線程是該鎖持有者 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }//(10) return false; } }
如上代碼公平性的tryAcquire策略與非公平鎖的相似,不一樣在於代碼(8)處設置CAS前添加了hasQueuedPredecessors 方法,該方法是實現公平性的核心代碼,源代碼以下:
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t &&((s = h.next) == null || s.thread != Thread.currentThread()); }
如上代碼所示,若是當前線程節點有前驅節點則返回true,不然若是當前AQS隊列爲空或者當前線程節點是AQS的第一個節點則返回false。
其中若是h == t 則說明當前隊列爲空則直接返回false,若是h != t 而且 (s = h.next) ==null 說明有一個元素將要做爲AQS的第一個節點入隊列,那麼返回true, 若是h != t 而且 (s = h.next) !=null 而且 s.thread != Thread.currentThread() 則說明隊列裏面的第一個元素不是當前線程則返回 true。
2.void lockInterruptibly() 與 lock() 方法相似,不一樣在於該方法對中斷響應,就是當前線程在調用該方式時候,若是其它線程調用了當前線程線程的 interrupt()方法,當前線程會拋出 InterruptedException 異常而後返回,源代碼以下:
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public final void acquireInterruptibly(int arg)throws InterruptedException { //當前線程被中斷則直接拋出異常 if (Thread.interrupted()) throw new InterruptedException(); //嘗試獲取資源 if (!tryAcquire(arg)) //調用AQS可被狀態的方法 doAcquireInterruptibly(arg); }
3.boolean tryLock() 嘗試獲取鎖,若是當前該鎖沒有被其它線程持有則當前線程獲取該鎖並返回 true, 否者返回 false,注意該方法不會引發當前線程阻塞。源碼以下所示:
public boolean tryLock() { return sync.nonfairTryAcquire(1); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
如上代碼與非公平鎖的 tryAcquire() 方法相似,因此 tryLock() 使用的是非公平策略。
4.boolean tryLock(long timeout, TimeUnit unit) 嘗試獲取鎖與 tryLock()不一樣在於設置了超時時間,若是超時沒有獲取該鎖則返回 false。源代碼以下:
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException { //調用AQS的tryAcquireNanos方法。 return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
接下來咱們要看一下,ReentrantLock是如何釋放鎖的。
1.void unlock() 嘗試釋放鎖,若是當前線程持有該鎖,調用該方法會讓該線程對該線程持有的 AQS 狀態值減一,若是減去 1 後當前狀態值爲 0 則當前線程會釋放對該鎖的持有,否者僅僅減一而已。
若是當前線程沒有持有該鎖調用了該方法則會拋出 IllegalMonitorStateException 異常 ,源代碼以下:
public void unlock() { sync.release(1); } protected final boolean tryRelease(int releases) { //(11)若是不是鎖持有者調用UNlock則拋出異常。 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //(12)若是當前可重入次數爲0,則清空鎖持有線程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } //(13)設置可重入次數爲原始值-1 setState(c); return free; }
如上代碼所示(11)若是當前線程不是該鎖持有者則直接拋異常,不然,看狀態值剩餘值是否爲0,爲0 則說明當前線程要釋放對該鎖的持有權,則執行代碼(12)把當前鎖持有者設置爲null。
若是剩餘值不爲0,則僅僅讓當前線程對該鎖的可重入次數減1。
到目前基本瞭解了ReentrantLock的原理,那麼接下來咱們是否能夠用ReentrantLock來實現一個簡單的線程安全的list呢?
例子以下:
import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** * Created by cong on 2018/6/12. */ public class ReentrantLockList { //線程不安全的list private ArrayList<String> array = new ArrayList<String>(); //獨佔鎖 private volatile ReentrantLock lock = new ReentrantLock(); //添加元素 public void add(String e) { lock.lock(); try { array.add(e); } finally { lock.unlock(); } } //刪元素 public void remove(String e) { lock.lock(); try { array.remove(e); } finally { lock.unlock(); } } //獲取數據 public String get(int index) { lock.lock(); try { return array.get(index); } finally { lock.unlock(); } } }
如上代碼經過在操做 array 元素前進行加鎖保證同時只有一個線程能夠對 array 數組進行修改,可是同時也只能有一個線程對 array 元素進行訪問。
最後幾個圖加深前面所學的內容,以下圖所示:
如上圖,假如線程 Thread1,Thread2,Thread3 同時嘗試獲取獨佔鎖 ReentrantLock,假設 Thread1 獲取到了,則 Thread2 和 Thread3 就會被轉換爲 Node 節點後放入 ReentrantLock 對應的 AQS 阻塞隊列後阻塞掛起。
如上圖,假設 Thread1 獲取鎖後調用了對應的鎖建立的條件變量 1,那麼 Thread1 就會釋放獲取到的鎖,而後當前線程就會被轉換爲 Node 節點後插入到條件變量 1 的條件隊列,因爲 Thread1 釋放了鎖,
因此阻塞到 AQS 隊列裏面 Thread2 和 Thread3 就有機會獲取到該鎖,假如使用的公平策略,那麼這時候 Thread2 會獲取到該鎖,會從 AQS 隊列裏面移除 Thread2 對應的 Node 節點。