概述
在 JDK 1.5 以前,鎖的實現只能用 synchronized 關鍵字;1.5 開始提供了 ReentrantLock,它是 API 層面的鎖。先看下 ReentrantLock 的類簽名以及如何使用:
public class ReentrantLock implements Lock, java.io.Serializable {}
典型用法:
public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } }
該用法和使用 synchronized 關鍵字效果是一樣的。既然有了 synchronized,為什麼又會有 Lock 呢?相比於 synchronized,其實 ReentrantLock 的出現並不重複,它增加了不少功能,下面先簡單介紹幾個概念。
公平鎖&非公平鎖:所謂鎖是否公平,簡單理解就是一系列執行緒獲取到鎖的順序是否遵循「先來後到」。即,如果先申請鎖的執行緒先獲取到鎖,就是公平鎖;否則就是非公平鎖。ReentrantLock 的預設實現和 synchronized 都是非公平鎖。
可重入鎖:鎖是否可重入,就是一個執行緒是否可以多次獲取同一個鎖,若是,就是可重入鎖。ReentrantLock 和 synchronized 都是可重入鎖。
程式碼分析
構造器
ReentrantLock 有兩個構造器,分別如下:
private final Sync sync; // 構造一個 ReentrantLock 例項(非公平鎖) public ReentrantLock() { sync = new NonfairSync(); } // 構造一個 ReentrantLock 例項(指定是否公平) public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
可以看到,兩個構造器都是初始化一個 Sync 型別的成員變數。而且,當 boolean 值 fair 為 true 時,初始化的 sync 為 FairSync,為 false 時初始化為 NonFairSync,二者分別表示「公平鎖」和「非公平鎖」。可以看到無參構造預設是非公平鎖。
常用方法
ReentrantLock 常用的方法就是 Lock 介面定義的幾個方法,如下:
// 獲取鎖(阻塞式) public void lock() { sync.lock(); } // 獲取鎖(響應中斷) public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } // 嘗試獲取鎖 public boolean tryLock() { return sync.nonfairTryAcquire(1); } // 嘗試獲取鎖(有超時等待) public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } // 釋放鎖 public void unlock() { sync.release(1); }
可以看到,這幾個方法內部都是通過呼叫 Sync 類(或其子類)的方法來實現,因此先從 Sync 類入手分析,程式碼如下(部分省略):
// 抽象類,繼承了 AQS abstract static class Sync extends AbstractQueuedSynchronizer { // 獲取鎖的方法,由子類實現 abstract void lock(); // 非公平鎖的 tryLock 方法實現 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 獲取 AQS 的 state 變數 int c = getState(); // 若為 0,表示當前沒有被其他執行緒佔用 if (c == 0) { // CAS 修改 state,若修改成功,表示成功獲取資源 if (compareAndSetState(0, acquires)) { // 將當前執行緒設定為 owner,到這裡表示當前執行緒成功獲取資源 setExclusiveOwnerThread(current); return true; } } // state 不為 0,且 owner 為當前執行緒 // 表示當前執行緒已經獲取到了資源,這裡表示“重入” else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // 修改 state 值(因為當前執行緒已經獲取資源,不存在競爭,因此無需 CAS 操作) setState(nextc); return true; } return false; } // 釋放鎖操作(對 state 做減法) 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; // 成功釋放後將 owner 設為空 setExclusiveOwnerThread(null); } // 修改 state 的值 // PS: 因為可能存在“重入”,因此一次釋放操作後當前執行緒仍有可能佔用資源, // 所以不會直接把 state 設為 0 setState(c); return free; } // 其他方法... final boolean isLocked() { return getState() != 0; } }
Sync 類繼承自 AQS,其中 nonfairTryAcquire 方法是非公平鎖 tryAcquire 方法的實現。
從上面程式碼可以看出,鎖的獲取和釋放是通過修改 AQS 的 state 變數來實現的。lock 方法可以看做對 state 執行“加法”操作,而 unlock 可以看做對 state 執行“減法”操作,當 state 為 0 時,表示當前沒有執行緒佔用資源。
公平鎖&非公平鎖
(1)非公平鎖 NonFairSync:
static final class NonfairSync extends Sync { final void lock() { // CAS 嘗試將 state 值修改為 1 if (compareAndSetState(0, 1)) // 若修改成功,則將當前執行緒設為 owner,表示成功獲取鎖 setExclusiveOwnerThread(Thread.currentThread()); // 若獲取失敗,則執行 AQS 的 acquire 方法(獨佔模式獲取資源) else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
可以看到,非公平鎖的 lock 操作為:先嚐試以 CAS 方式修改 state 的值,若修改成功,則表示成功獲取到鎖,將 owner 設為當前執行緒;否則就執行 AQS 中的 acquire 方法,具體可參考前文「JDK原始碼分析-AbstractQueuedSynchronizer(2)」,這裡不再贅述。
(2)公平鎖 FairSync:
static final class FairSync extends Sync { final void lock() { acquire(1); } // 公平鎖的 tryAcquire 實現 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // state 為 0,表示資源未被佔用 if (c == 0) { // 若佇列中有其他執行緒在排隊等待,則返回 false,表示獲取失敗; // 否則,再嘗試去修改 state 的值 // PS: 這裡是公平鎖與非公平鎖的區別所在 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 若當前執行緒已佔用了鎖,則“重入” else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
可以看到,與非公平鎖相比,公平鎖的不同之處在於增加了判斷條件 hasQueuedPredecessors,即首先判斷主佇列中是否有其他執行緒在等待,當沒有其他執行緒在排隊時再去獲取,否則獲取失敗。
hasQueuedPredecessors 在 AQS 中實現如下:
/** * Queries whether any threads have been waiting to acquire longer * than the current thread. */ public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. 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()); }
小結
synchronized 與 ReentrantLock 比較:
相同點:二者都是互斥鎖,可重入,預設都是非公平鎖。
不同點:synchronized 是語法層面實現,自動獲取鎖和釋放鎖;ReentrantLock 是 API 層面實現,手動獲取鎖和釋放鎖。
ReentrantLock 相比 synchronized 的優勢:
1. 可響應中斷;
2. 獲取鎖可設定超時;
3. 可實現公平鎖;
4. 可繫結多個條件(Condition)。
JDK 1.6 以後,synchronized 與 ReentrantLock 效能基本持平,JVM 未來的效能優化也會更偏向於原生的 synchronized。因此,如何選擇還要根據實際需求,效能不再是不選擇 synchronized 的原因了。
相關閱讀:
JDK原始碼分析-AbstractQueuedSynchronizer(2)
Stay hungry, stay foolish.
PS: 本文首發於微信公眾號【WriteOnRead】。