一、前言
在分析了AbstractQueuedSynchronier原始碼後,接著分析ReentrantLock原始碼,其實在AbstractQueuedSynchronizer的分析中,已經提到過ReentrantLock,ReentrantLock表示下面具體分析ReentrantLock原始碼。
二、ReentrantLock資料結構
ReentrantLock的底層是藉助AbstractQueuedSynchronizer實現,所以其資料結構依附於AbstractQueuedSynchronizer的資料結構,關於AQS的資料結構,在前一篇已經介紹過,不再累贅。
三、ReentrantLock原始碼分析
3.1 類的繼承關係
public class ReentrantLock implements Lock, java.io.Serializable
說明:ReentrantLock實現了Lock介面,Lock介面中定義了lock與unlock相關操作,並且還存在newCondition方法,表示生成一個條件。
3.2 類的內部類
ReentrantLock總共有三個內部類,並且三個內部類是緊密相關的,下面先看三個類的關係。
說明:ReentrantLock類內部總共存在Sync、NonfairSync、FairSync三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類。下面逐個進行分析。
1. Sync類
Sync類的原始碼如下
abstract static class Sync extends AbstractQueuedSynchronizer { // 序列號 private static final long serialVersionUID = -5179523762034025860L; // 獲取鎖 abstract void lock(); // 非公平方式獲取 final boolean nonfairTryAcquire(int acquires) { // 當前執行緒 final Thread current = Thread.currentThread(); // 獲取狀態 int c = getState(); if (c == 0) { // 表示沒有執行緒正在競爭該鎖 if (compareAndSetState(0, acquires)) { // 比較並設定狀態成功,狀態0表示鎖沒有被佔用 // 設定當前執行緒獨佔 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; } // 試圖在共享模式下獲取物件狀態,此方法應該查詢是否允許它在共享模式下獲取物件狀態,如果允許,則獲取它 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) // 當前執行緒不為獨佔執行緒 throw new IllegalMonitorStateException(); // 丟擲異常 // 釋放標識 boolean free = false; if (c == 0) { free = true; // 已經釋放,清空獨佔 setExclusiveOwnerThread(null); } // 設定標識 setState(c); return free; } // 判斷資源是否被當前執行緒佔有 protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } // 新生一個條件 final ConditionObject newCondition() { return new ConditionObject(); } // Methods relayed from outer class // 返回資源的佔用執行緒 final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } // 返回狀態 final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } // 資源是否被佔用 final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ // 自定義反序列化邏輯 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } }
說明:Sync類存在如下方法和作用如下。
2. NonfairSync類
NonfairSync類繼承了Sync類,表示採用非公平策略獲取鎖,其實現了Sync類中抽象的lock方法,原始碼如下。
// 非公平鎖 static final class NonfairSync extends Sync { // 版本號 private static final long serialVersionUID = 7316153563782823691L; // 獲得鎖 final void lock() { if (compareAndSetState(0, 1)) // 比較並設定狀態成功,狀態0表示鎖沒有被佔用 // 把當前執行緒設定獨佔了鎖 setExclusiveOwnerThread(Thread.currentThread()); else // 鎖已經被佔用,或者set失敗 // 以獨佔模式獲取物件,忽略中斷 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
說明:從lock方法的原始碼可知,每一次都嘗試獲取鎖,而並不會按照公平等待的原則進行等待,讓等待時間最久的執行緒獲得鎖。
3. FairSyn類
FairSync類也繼承了Sync類,表示採用公平策略獲取鎖,其實現了Sync類中的抽象lock方法,原始碼如下。
// 公平鎖 static final class FairSync extends Sync { // 版本序列化 private static final long serialVersionUID = -3000897897090466540L; final void lock() { // 以獨佔模式獲取物件,忽略中斷 acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ // 嘗試公平獲取鎖 protected final boolean tryAcquire(int acquires) { // 獲取當前執行緒 final Thread current = Thread.currentThread(); // 獲取狀態 int c = getState(); if (c == 0) { // 狀態為0 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 不存在已經等待更久的執行緒並且比較並且設定狀態成功 // 設定當前執行緒獨佔 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 狀態不為0,即資源已經被執行緒佔據 // 下一個狀態 int nextc = c + acquires; if (nextc < 0) // 超過了int的表示範圍 throw new Error("Maximum lock count exceeded"); // 設定狀態 setState(nextc); return true; } return false; } }
說明:跟蹤lock方法的原始碼可知,當資源空閒時,它總是會先判斷sync佇列(AbstractQueuedSynchronizer中的資料結構)是否有等待時間更長的執行緒,如果存在,則將該執行緒加入到等待佇列的尾部,實現了公平獲取原則。其中,FairSync類的lock的方法呼叫如下,只給出了主要的方法。
說明:可以看出只要資源被其他執行緒佔用,該執行緒就會新增到sync queue中的尾部,而不會先嚐試獲取資源。這也是和Nonfair最大的區別,Nonfair每一次都會嘗試去獲取資源,如果此時該資源恰好被釋放,則會被當前執行緒獲取,這就造成了不公平的現象,當獲取不成功,再加入佇列尾部。
3.3 類的屬性
public class ReentrantLock implements Lock, java.io.Serializable { // 序列號 private static final long serialVersionUID = 7373984872572414699L; // 同步佇列 private final Sync sync; }
說明:ReentrantLock類的sync非常重要,對ReentrantLock類的操作大部分都直接轉化為對Sync和AbstractQueuedSynchronizer類的操作。
3.4 類的建構函式
1. ReentrantLock()型建構函式
public ReentrantLock() { // 預設非公平策略 sync = new NonfairSync(); }
說明:可以看到預設是採用的非公平策略獲取鎖。
2. ReentrantLock(boolean)型建構函式
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
說明:可以傳遞引數確定採用公平策略或者是非公平策略,引數為true表示公平策略,否則,採用非公平策略。
3.5 核心函式分析
通過分析ReentrantLock的原始碼,可知對其操作都轉化為對Sync物件的操作,由於Sync繼承了AQS,所以基本上都可以轉化為對AQS的操作。如將ReentrantLock的lock函式轉化為對Sync的lock函式的呼叫,而具體會根據採用的策略(如公平策略或者非公平策略)的不同而呼叫到Sync的不同子類。
所以可知,在ReentrantLock的背後,是AQS對其服務提供了支援,由於之前我們分析AQS的核心原始碼,遂不再累贅。下面還是通過例子來更進一步分析原始碼。
四、示例分析
4.1 公平鎖
package com.hust.grid.leesf.abstractqueuedsynchronizer; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class MyThread extends Thread { private Lock lock; public MyThread(String name, Lock lock) { super(name); this.lock = lock; } public void run () { lock.lock(); try { System.out.println(Thread.currentThread() + " running"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } } public class AbstractQueuedSynchonizerDemo { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(true); MyThread t1 = new MyThread("t1", lock); MyThread t2 = new MyThread("t2", lock); MyThread t3 = new MyThread("t3", lock); t1.start(); t2.start(); t3.start(); } }
執行結果(某一次):
Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running
說明:該示例使用的是公平策略,由結果可知,可能會存在如下一種時序。
說明:首先,t1執行緒的lock操作 -> t2執行緒的lock操作 -> t3執行緒的lock操作 -> t1執行緒的unlock操作 -> t2執行緒的unlock操作 -> t3執行緒的unlock操作。根據這個時序圖來進一步分析原始碼的工作流程。
① t1執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。
說明:由呼叫流程可知,t1執行緒成功獲取了資源,可以繼續執行。
② t2執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。
說明:由上圖可知,最後的結果是t2執行緒會被禁止,因為呼叫了LockSupport.park。
③ t3執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。
說明:由上圖可知,最後的結果是t3執行緒會被禁止,因為呼叫了LockSupport.park。
④ t1執行緒呼叫了lock.unlock,下圖給出了方法呼叫中的主要方法。
說明:如上圖所示,最後,head的狀態會變為0,t2執行緒會被unpark,即t2執行緒可以繼續執行。此時t3執行緒還是被禁止。
⑤ t2獲得cpu資源,繼續執行,由於t2之前被park了,現在需要恢復之前的狀態,下圖給出了方法呼叫中的主要方法。
說明:在setHead函式中會將head設定為之前head的下一個結點,並且將pre域與thread域都設定為null,在acquireQueued返回之前,sync queue就只有兩個結點了。
⑥ t2執行lock.unlock,下圖給出了方法呼叫中的主要方法。
說明:由上圖可知,最終unpark t3執行緒,讓t3執行緒可以繼續執行。
⑦ t3執行緒獲取cpu資源,恢復之前的狀態,繼續執行。
說明:最終達到的狀態是sync queue中只剩下了一個結點,並且該節點除了狀態為0外,其餘均為null。
⑧ t3執行lock.unlock,下圖給出了方法呼叫中的主要方法。
說明:最後的狀態和之前的狀態是一樣的,佇列中有一個空節點,頭結點為尾節點均指向它。
使用公平策略和Condition的情況可以參考上一篇關於AQS的原始碼示例分析部分,不再累贅。
五、總結
再掌握了AQS後,再來分析ReentrantLock的原始碼,就會非常簡單,因為ReentrantLock的絕大部分操作都是基於AQS類的。所以,進行分析時要找準方向,就會事半功倍。謝謝各位園友觀看~