前言
ReentrantLock和synchronized一樣都是實作執行緒同步,但是像比synchronized它更加靈活、強大、增加了輪詢、超時、中斷等高級功能,可以更加精細化的控制執行緒同步,它是基于AQS實作的鎖,他支持公平鎖和非公平鎖,同時他也是可重入鎖和自旋鎖,本章將基于原始碼來探索一下ReentrantLock的加鎖機制,文中如果存在理解不到位的地方,還請提出寶貴意見共同探討,不吝賜教,
公平鎖和非公平鎖的加鎖機制流程圖:

一、ReentrantLock的公平鎖
使用ReentrantLock的公平鎖,呼叫lock進行加鎖,lock方法的原始碼如下:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看到,FairLock首先呼叫了tryAcquire,tryAcquire原始碼如下:
/**
* 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) {
//如果佇列中不存在等待的執行緒或者當前執行緒在佇列頭部,則基于CAS進行加鎖
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;
}
從原始碼中可以看到,當state為0,即沒有執行緒獲取到鎖時,FairLock首先會呼叫hasQueuedPredecessors()方法檢查佇列中是否有等待的執行緒或者自己是否在佇列頭部,如果佇列中不存在等待的執行緒或者自己在佇列頭部則呼叫compareAndSetState()方法基于CAS操作進行加鎖,如果CAS操作成功,則呼叫setExclusiveOwnerThread設定加鎖執行緒為當前執行緒,當state不為0,即有執行緒占用鎖的時候會判斷占有鎖的執行緒是否是當前執行緒,如果是的話則可以直接獲取到鎖,這就是ReentrantLock是可重入鎖的體現,
如果通過呼叫tryAcquire沒有獲取到鎖,從原始碼中我們可以看到,FairLock會呼叫addWaiter()方法將當前執行緒加入CLH佇列中,addWaiter方法原始碼如下:
private Node addWaiter(Node mode) {
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;
//基于CAS將當前執行緒節點加入佇列尾部
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果CAS操作失敗,則呼叫enq自旋加入佇列
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在addWaiter方法中,會CAS操作將當前執行緒節點加入佇列尾部,如果第一次CAS失敗,則會呼叫enq方法通過自旋的方式,多次嘗試進行CAS操作將當前執行緒加入佇列,
將當前執行緒加入佇列之后,會呼叫acquireQueued方法實作當前執行緒的自旋加鎖,acquireQueued原始碼如下:
final boolean acquireQueued(final Node node, int arg) {
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);
}
}
在acquireQueued方法中每次自旋首先會呼叫predecessor()方法獲取,當前執行緒節點的前節點,如果發現前節點是head節點,則說明當前執行緒節點處于對頭(head是傀儡節點),那么則呼叫tryAcquire盡心加鎖,如果當前執行緒節點不在佇列頭部,那么則會呼叫shouldParkAfterFailedAcquire方法判斷當前執行緒節點是否可以掛起知道前節點釋放鎖時喚醒自己,如果可以掛起,則呼叫parkAndCheckInterrupt實作掛起操作,
shouldParkAfterFailedAcquire原始碼如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire原始碼中,如果當前執行緒節點的前節點的waitStatus狀態為SIGNAL(-1)時,表明前節點已經設定了釋放鎖時喚醒(unpark)它的后節點,那么當前執行緒節點可以安心阻塞(park),等待它的前節點在unlock時喚醒自己繼續嘗試加鎖,如果前節點的waitStatus狀態>0,即為CANCELLED (1),表明前節點已經放棄了獲取鎖,那么則會繼續往前找,找到一個能夠在unlock時喚醒自己的執行緒節點為止,如果前節點waitStatus狀態是CONDITION (-2),即處于等待條件的狀態,則會基于CAS嘗試設定前節點狀態為SIGNAL(主動干預前節點達到喚醒自己的目的),
parkAndCheckInterrupt原始碼:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
二、ReentrantLock的非公平鎖
和公平鎖加鎖機制不同的是,非公平鎖一上來不管佇列中是否還存在執行緒,就直接使用CAS操作進行嘗試加鎖(這就是它的非公平的體現),原始碼如下:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
如果CAS操作失敗(一上來就吃了個閉門羹),則呼叫acquire方法進行后續的嘗試和等待,從原始碼中可以看到,首先回呼叫tryAcquire方法進行再次嘗試加鎖或者鎖重入,NoFairLockd的tryAcquire方法原始碼如下:
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;
}
可以看到NoFairLock的tryAcquire方法和FairLock的tryAcquire方法唯一不同之處是NoFairLock中嘗試加鎖前不需要呼叫hasQueuedPredecessors方法判斷佇列中是否存在其他執行緒,而是直接進行CAS操作加鎖,那么如果再次嘗試加鎖或者鎖重入失敗,則會進行后續的和公平鎖完全一樣的操作流程(不再贅述),即:加入佇列(addWaiter)–>自旋加鎖(acquireQueued),
三、unlock解鎖
說完了公平鎖和非公平鎖的加鎖機制,我們再順帶簡單的看看解鎖原始碼,unlock原始碼如下:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//嘗試釋放鎖
if (tryRelease(arg)) {
Node h = head;
//鎖釋放成后喚醒后邊阻塞的執行緒節點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
總結
本文主要探索了公平鎖和非公平鎖的加鎖流程,他們獲取鎖的不同點和相同點,整篇文章涉及到了以下幾點:
1.公平鎖、非公平鎖加鎖程序
2.自旋鎖的實作以及自旋程序中的阻塞喚醒
3.可重入鎖的實作
4.CLH佇列
注:文中如果存在理解不到位的地方還請提出寶貴意見,不吝賜教,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266660.html
標籤:java
