這篇文章不是講ReentrantLock的使用,而是通過調試,分析其實現原理。
非公平鎖
測試代碼如下:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " has been acquired the lock...");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " has been released the lock...");
}
}
public static void main(String[] args) {
ReentrantLockExample lockExample = new ReentrantLockExample();
Thread A = new Thread(()->{
lockExample.doSomething();
}, "thread-A");
Thread B = new Thread(()->{
lockExample.doSomething();
}, "thread-B");
Thread C = new Thread(()->{
lockExample.doSomething();
}, "thread-C");
Thread D = new Thread(()->{
lockExample.doSomething();
}, "thread-D");
A.start();
B.start();
C.start();
D.start();
}
}
代碼測試的是4個線程爭搶一把鎖。
1. 加鎖流程解析
通過IDEA多線程調試工具進行調試,調試模擬情景為:A-B-C-D依次獲取鎖,等所有的獲取請求結束,再依次釋放鎖。
第一步:線程A請求鎖
調用ReentrantLock的lock方法:
public void lock() {
sync.lock();
}
執行到此出,將處理交給sync,我們這里是非公平鎖,則sync為NonfairSync,調用方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
通過CAS設置state變量,期望當前值是0,更新值是1。state初始值為0,無其他線程更改過,所以這里CAS設置成功,state值變為1。并且exclusiveOwnerThread設置為thread-A對象。
執行圖:
lock對象內容:
第二步:線程B請求鎖
掛起A線程,執行B線程。
線程B執行到此代碼:
ReentrantLock.java
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
此時state=1,此處CAS執行失敗,接下來執行else語句代碼。
acquire方法在AbstractQueuedSynchronizer類中定義:
AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
if條件中有兩處,若第一次為false,則第二處會執行。
先來看tryAcquire, tryAcquire在AbstractQueuedSynchronizer重定義,但是沒有定義具體實現,具體實現交給子類,我們這里就是NonfairSync類實現。
NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
nonfairTryAcquire在NonfairSync直接父類也就是Sync中定義:
Sync
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;
}
此時state=1,exclusiveOwnerThread=thread-A, current = thread-B。此方法內邏輯不執行,返回false。
返回執行acquireQueued方法。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
執行這個方法前要先執行addWaiter方法,此方法用于創建即將入同步隊列的Node(節點)。
AbstractQueueSynchronizer.java
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
此時tail = null,執行enq方法。
AbstractQueueSynchronizer.java
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;
}
}
}
}
- 第一次循環:創建一個Node對象,設置給head。同時將head賦值給tail,這樣head和tail指向同一個對象。兩者不再為null。
- 第二次循環:將B節點的前置設置為t(t是中間變量指向的就是head/tail節點),然后將B設置為tail。執行完成后返回t,此時t和head執行同一個對象,其實返回的就是head節點。
執行完成后,head和tail的內容如下:
注意一下,雖然addWaiter方法返回的是頭節點,但是node還是指向的B節點。接著執行acquireQueued方法。
AbstractQueuedSynchronizer.java
final boolean acquireQueued(final Node node, int arg) {// Node此時是B節點
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);
}
}
- 第一次循環:p為head節點,tryAcquire和前面一樣返回false,第一個if語句不行。緊接著執行shouldParkAfterFailedAcquire方法,方法代碼如下:
AbstractQueuedSynchronzier.java
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;
}
此時pred=head, node = B, ws = 0, 執行:
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
這個執行完成后,head的waitStatus被設置成Node.SINGAL,這個表示head節點的后繼者也就是B節點代表的線程需要被通知。返回false。
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
if代碼塊不執行。
- 第二次循環: p還是head節點,tryAcquire和第一次循環一樣,第一個if依然不執行,繼續執行shouldParkAfterFailedAcquire,此時shouldParkAfterFailedAcquire方法內pre是head節點,而head節點經過第一次循環waitStatus被設置為-1。shouldParkAfterFailedAcquire(p, node)返回true, 則接下來執行parkAndCheckInterrupt()。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
線程B執行到LockSupport.park(this)處被掛起。
執行圖:
第三步:線程C請求鎖
注意:此時線程A是我們主動(通過IDE操作)掛起的(RUNNING),線程B通過上一步的操作被動(park操作)掛起的(WAITTING)。
因為前面已經見到一些邏輯的執行了,這里就不再重復,直接講關系到Node的操作。
通過addWaiter操作,為線程C創建一個Node節點:
AbstractQueuedSynchronzier.java
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
此時tail為B節點,然后將C節點的prev設置為B,通過CAS操作將C節點設置為tail,設置成功將B的next設置為C。然后直接返回C節點。
執行完成后head和tail的內容如下:
addWaiter返回C節點后,執行acquireQueued方法:
AbstractQueuedSynchronzier.java
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);
}
}
此時node=C
- 第一次循環: C的前置節點是B,這里p=B,第一個if不執行,執行
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;
}
pre=B,B的waitStatus=0,則執行compareAndSetWaitStatus(pred, ws, Node.SIGNAL),B的waitStatus被設為-1,表示后繼節點,即C節點所代表的線程需要被喚醒。繼續執行線程C被掛起。
執行圖:
第四步:線程D請求鎖
線程D請求鎖和上面的流程幾乎是一樣了,這里就不再分別調試,執行圖: