Reentrant 2
前兩篇寫完了後我自己研究了下,還有有很多疑惑和問題,這篇就繼續以自問自答的方式寫
如果沒看過第1篇的可以先看看那個https://www.cnblogs.com/sunankang/p/16458795.html
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
進入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);
}
}
第一個問題
interrupted這個變數的作用
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
在parkAndCheckInterrupt
方法中最後return的是這個執行緒是否被打斷,它的作用是啥?
先來回顧interrupt()
,interrupted()
和isInterrupted()
三者區別,長得很像,注意區分
interrupt()
的作用是中斷執行緒,如果被中斷的執行緒處於阻塞狀態下,例如呼叫wait()
,join()
sleep()
,則丟擲異常,否則只是設定一箇中斷標記為true,注意:僅僅是設定中斷狀態為true,並不會去 "中斷" 執行緒
interrupted()
獲取執行緒的中斷狀態並且清空中斷狀態(將中斷狀態設定為false)
isInterrupted()
獲取執行緒的中斷狀態並不會清除中斷狀態
呼叫 interrupt 會使park方法立即結束,可以理解為喚醒
繼續程式碼,看這個變數最後到了哪裡
情況1 沒有被打斷過
假設執行緒沒有被中斷過,那麼parkAndCheckInterrupt
返回就是false
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);
}
}
那麼不進入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
這個if,獲取到鎖後返回false,回到acquire
方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
因為false,所以不進入selfInterrupt()
,方法結束
情況2 park或準備park,被喚醒後直接獲取到了鎖
先證明一下打斷是會喚醒park中的執行緒的
我就再重複粘一下程式碼了,方便看
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
那麼返回的就是true,回到上級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);
}
}
因為返回true,所以進入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
將interrupted返回true
假設迴圈獲取到鎖,那麼再返回上一級acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
那麼進入selfInterrupt()
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
是不是有點疑惑?我如果沒有呼叫過interrupt()
那ReentrantLock就不做任何操作,我如果呼叫了,那它再給我呼叫一次 ???? 還有情況3
情況3 park或準備park,被喚醒後沒有獲取到鎖
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;
}
//假設在呼叫shouldParkAfterFailedAcquire成功後,馬上就要呼叫parkAndCheckInterrupt 時間片用完了
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
那麼這個時候interrupted屬性就有用了
首先要知道一點,一個被中斷的執行緒是無法park的,除非清除了中斷狀態,即設定為將中斷狀態設定為false, 口說無憑,直接上圖
第二張圖還是在park狀態,證明了被打斷的執行緒是無法park的,除非將它中斷狀態設定為false
那麼回到程式碼中就能知道這個的作用
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);
}
}
如果執行緒被打斷喚醒,還是在for(;;)
中,還是去獲取鎖,假設沒有獲取到呢?那麼就一直在for迴圈中嘎嘎跑,因為執行緒的狀態是被中斷的,無法再次park了
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
那麼現在懂了最後的Thread.interrupted()
作用了嗎,就是將中斷狀態設定回false,好讓執行緒沒有獲取到鎖繼續park
那這時候可能就問了:那你ReentrantLock把中斷狀態給我清空了,我自己如果有需要根據中斷狀態來判斷的程式碼咋辦啊?
好,我們們從park先被打斷來捋一下
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
因為被打斷,執行緒醒來,執行Thread.interrupted()
並清空中斷狀態,返回true
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);
}
}
因為返回的是true,所以進入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
的程式碼塊,將interrupted
屬性設定為true
那麼for(;;)
迴圈再來一次,如果沒有獲取到鎖.繼續park,直到被喚醒,走tryAcquire()
獲取到為止,那麼此時interrupted
變數就為true了
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
那麼退出acquireQueued()
方法回到acquire()
中,因為acquireQueued()
返回的是true,所以進入selfInterrupt()
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
所以懂了嗎?