前言
本篇是基於JDK8版本,分析ReentrantLock公平鎖的獲取和釋放的源碼。
1、獲取公平鎖
final void lock() {
//ReentrantLock是可重入鎖,所以單個線程每次進入都+1,初始值是0
acquire(1);
}
public final void acquire(int arg) {
//這裏總共有4個方法,下面會分別進行詳細分析
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.1、tryAcquire方法
tryAcquire方法的作用就是嘗試去獲取鎖,獲取成功返回true,獲取失敗則返回false。
protected final boolean tryAcquire(int acquires) {
//獲取當前線程
final Thread current = Thread.currentThread();
//獲取鎖狀態,就是每次線程進入都要加1的那個值
int c = getState();
//c等於0,就是初始狀態,鎖沒有被佔用
if (c == 0) {
//判斷隊列中當前線程是否是第一個
//CAS再次判斷鎖是否沒有被佔用
//設置鎖被當前線程佔用,然後返回
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果鎖的擁有者是當前線程
//鎖的狀態+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//更新鎖的狀態,然後返回
setState(nextc);
return true;
}
return false;
}
1.2、addWaiter方法
addWaiter方法是創建當前線程的Node節點,並在Node中記錄鎖是“獨佔鎖”類型,並且將該節點添加到隊列的末尾,當前線程就處於等待狀態了,我們先看看Node節點的源碼:
private transient volatile Node head; // CLH隊列的隊首
private transient volatile Node tail; // CLH隊列的隊尾
// 隊列的Node節點
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 線程已被取消,對應的waitStatus的值
static final int CANCELLED = 1;
// 當前線程的後一個線程需要被喚醒,對應的waitStatus值
static final int SIGNAL = -1;
// 線程休眠,對應的waitStatus的值
static final int CONDITION = -2;
// 其它線程獲取到“共享鎖”,對應的waitStatus的值
static final int PROPAGATE = -3;
volatile int waitStatus;
// 前一節點
volatile Node prev;
// 後一節點
volatile Node next;
// 節點所對應的線程
volatile Thread thread;
// 標記隊列是獨佔鎖還是共享鎖
Node nextWaiter;
//共享鎖則返回true,獨佔鎖則返回false。
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回前一節點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
看完Node源碼,下面我們再看看addWaiter方法
private Node addWaiter(Node mode) {
//創建當前線程Node節點
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//判斷隊列是否爲空,不爲空則添加到隊列末尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//爲空,則新建一個隊列,並添加進去
enq(node);
return node;
}
1.3、acquireQueued方法
acquireQueued方法是在隊列中獲取鎖
final boolean acquireQueued(final Node node, int arg) {
//標記acquire是否成功
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,表示曾經中斷過
interrupted = true;
}
} finally {
if (failed)
//如果tryAcquire出現異常那麼取消當前結點的獲取
cancelAcquire(node);
}
}
1.4、selfInterrupt方法
這個方法很簡單,就是中斷當前線程
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2、釋放公平鎖
public void unlock() {
//這裏的1和獲取鎖的1是相同的,釋放鎖的時候是-1
sync.release(1);
}
2.1、release方法
public final boolean release(int arg) {
//tryRelease來嘗試釋放當前線程持有的鎖。
//成功的話,則喚醒後繼等待線程,並返回true。否則,直接返回false
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//喚醒當前線程的後繼線程
unparkSuccessor(h);
return true;
}
return false;
}
2.2、unparkSuccessor方法
private void unparkSuccessor(Node node) {
//獲取當前線程的狀態
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//循環獲取當前節點的後繼節點狀態小於等於0的節點
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//喚醒後繼對應節點
LockSupport.unpark(s.thread);
}
結束語
本篇詳細的分析了ReentrantLock獲取公平鎖和釋放公平鎖的代碼,大部分的代碼上面都進行了中文註釋。下一篇將介紹ReentrantLock的非公平鎖的獲取和釋放。
分析源碼不易,如果有幫助到你請隨手點個贊,謝謝大家!