死磕Java——ReentrantLock
ReentrantLock
是 java.util.concurrent.locks
包下一個可重入的預設是非公平的鎖, ReentrantLock
類是 Lock
介面的一個使用很頻繁的實現類,類結構如下圖:

前面說過 JMM
模型要求的是 可見性 , 原子性 和 有序性 。解決原子性的方法也有多種,例如 synchronized
同步方法或者同步程式碼塊,也可以使用 AtomicInteger
原子包裝類解決,都知道 synchronized
加鎖是最笨重的解決方法,所裡,這裡使用的是 ReentrantLock
加鎖來實現原子性,程式碼如下:
class MyData { int num = 0; Lock lock = new ReentrantLock(); public void add() { try { lock.lock(); num++; } finally { lock.unlock(); } } } public class ReentrantLockDemo { private static Logger log = LoggerFactory.getLogger(ReentrantLockDemo.class); public static void main(String[] args) { MyData myData = new MyData(); for (int i = 1; i <= 20; i++) { new Thread(() -> { for (int j = 1; j <= 1000; j++) { myData.add(); } }, String.valueOf(i)).start(); } // 知道所有的執行緒執行結束 while (Thread.activeCount() > 1) { Thread.yield(); } log.info("結果:{}", myData.num); } } 複製程式碼
1.1.ReentrantLock的公平性
ReentrantLock
提供了一個帶引數的建構函式,來讓使用者決定使用是否是公平鎖。

通過原始碼我們可以知道,無引數就是預設為非公平鎖,傳入 true
表示公平鎖,傳入 false
表示非公平鎖,原始碼如下
// 空參預設為非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } 複製程式碼
// 根絕引數決定鎖的公平性 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 複製程式碼
1.2.ReentrantLock的非公平鎖
1.2.1使用ReentrantLock加鎖
// 建立一個非公平鎖 Lock lock = new ReentrantLock(); try { // 加鎖 lock.lock(); } finally { // 釋放鎖 lock.unlock(); } 複製程式碼
1.2.2.執行的是ReentrantLock的lock方法
public void lock() { sync.lock(); } 複製程式碼
1.2.3.呼叫的是NonfairSync類的lock方法
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock.Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { // 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖 if (compareAndSetState(0, 1)) // 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回 setExclusiveOwnerThread(Thread.currentThread()); else // 獲取鎖失敗則執行該方法 acquire(1); } } 複製程式碼
1.2.4.呼叫AQS的cAS方法獲取鎖
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this // unsafe類是通過native方法直接操作記憶體資料 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } 複製程式碼
1.2.5.獲取鎖失敗執行acquire方法
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 複製程式碼
1.2.6.tryAcquire嘗試獲取鎖
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } 複製程式碼
tryAcquire
這個方法是 AQS
預設的鉤子方法,不同類的有不同的實現,其中 NonfairSync
實現如下:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } 複製程式碼
final boolean nonfairTryAcquire(int acquires) { // 傳入的acquires為1,獲取當前執行緒 final Thread current = Thread.currentThread(); // 獲取state變數的值,即當前鎖被重入的次數 int c = getState(); // state為0,說明當前鎖未被任何執行緒持有 if (c == 0) { // 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖 if (compareAndSetState(0, acquires)) { // 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回 setExclusiveOwnerThread(current); // 返回嘗試獲取鎖成功 return true; } } // //當前執行緒就是持有鎖的執行緒,說明該鎖被重入了 else if (current == getExclusiveOwnerThread()) { // //計算state變數要更新的值 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // //非同步方式更新state值 setState(nextc); // 獲取鎖成功,返回結果 return true; } // 嘗試獲取鎖失敗 return false; } 複製程式碼
1.2.7.獲取鎖失敗後將執行緒加入到同步佇列中
private Node addWaiter(Node mode) { // 創造一個新的節點,傳入的mode引數為null Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure // tail是指向佇列尾元素的尾指標,新節點的頭指標指向佇列的尾指標 Node pred = tail; // //佇列不為空 if (pred != null) { // 新節點的頭指標修改為佇列的尾指標 node.prev = pred; // 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 安全的加入同步佇列 enq(node); return node; } 複製程式碼
1.2.8.將執行緒加入同步佇列
private Node enq(final Node node) { for (;;) { // t節點指向當前佇列的最後一個節點 Node t = tail; // 佇列為空 if (t == null) { // Must initialize // 通過CAS構造新節點 if (compareAndSetHead(new Node())) // 尾指標指向新節點 tail = head; } else { // 佇列不為空時候,將節點的頭指標指向佇列的尾指標 node.prev = t; // 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標 if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } 複製程式碼
當佇列為空的時候通過CAS更新頭節點原始碼如下:
private final boolean compareAndSetHead(Node update) { return unsafe.compareAndSwapObject(this, headOffset, null, update); } 複製程式碼
說明:僅當佇列中原值為null時更新成功。
當佇列不為空的時候通過CAS更新尾節點原始碼如下:
private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update); } 複製程式碼
說明:CAS方式更新tial指標,僅當原值為t時更新成功
1.2.9.執行緒進入佇列後
final boolean acquireQueued(final Node node, int arg) { // 引數arg為1,節點是為獲取到鎖的執行緒節點 boolean failed = true; try { boolean interrupted = false; // 進入死迴圈,正常情況下執行緒只有獲得鎖才能跳出迴圈 for (;;) { // 獲取當前節點執行緒的前驅節點 final Node p = node.predecessor(); // 當獲取到了前驅節點為佇列頭節點或者嘗試獲取鎖成功 if (p == head && tryAcquire(arg)) { // 設定當前執行緒的節點為頭節點 setHead(node); p.next = null; // help GC failed = false; return interrupted; // 死迴圈的唯一出口 } if (shouldParkAfterFailedAcquire(p, node) && // 判斷是否要阻塞當前執行緒 parkAndCheckInterrupt()) // 阻塞當前執行緒 interrupted = true; } } finally { if (failed) cancelAcquire(node); } } 複製程式碼
1.3.ReentrantLock加鎖總結
1.4.ReentrantLock非公平解鎖
lock.unlock(); 複製程式碼
1.4.1.釋放鎖
public void unlock() { sync.release(1); } 複製程式碼
public final boolean release(int arg) { // 釋放鎖(state-1),若釋放後鎖可被其他執行緒獲取(state=0),返回true if (tryRelease(arg)) { // 獲取佇列的頭節點 Node h = head; //當前佇列不為空且頭結點狀態不為初始化狀態(0) if (h != null && h.waitStatus != 0) // 喚醒同步佇列中被阻塞的執行緒 unparkSuccessor(h); return true; } return false; } 複製程式碼
1.4.2.嘗試釋放鎖
protected final boolean tryRelease(int releases) { // 計算待更新的state值 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 待更新的state值為0,說明持有鎖的執行緒未重入,一旦釋放鎖其他執行緒將能獲取 if (c == 0) { free = true; // 清除鎖的持有執行緒標記 setExclusiveOwnerThread(null); } // 更新的state值 setState(c); return free; } 複製程式碼
我們可以看見一個很重要的抽象類 AbstractQueuedSynchronizer
,有關 AQS
稍後在死磕,可以說 AQS
是同步元件的基礎。