ReentrantLock的加鎖方法Lock()提供了無條件地輪詢獲取鎖的方式,lockInterruptibly()提供了可中斷的鎖獲取方式。這兩個方法的區別在哪里呢?通過分析源碼可以知道lock方法默認處理了中斷請求,一旦監測到中斷狀態,則中斷當前線程;而lockInterruptibly()則直接拋出中斷異常,由上層調用者區去處理中斷。
lock獲取鎖過程中,忽略了中斷,在成功獲取鎖之后,再根據中斷標識處理中斷,即selfInterrupt中斷自己。 acquire操作源碼如下:
/**
*默認處理中斷方式是selfInterrupt
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquireQueued,在for循環中無條件重試獲取鎖,直到成功獲取鎖,同時返回線程中斷狀態。該方法通過for循正常返回時,必定是成功獲取到了鎖。
/**
*無條件重試,直到成功返回,並且記錄中斷狀態
*/
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);
}
}
可中斷加鎖,即在鎖獲取過程中不處理中斷狀態,而是直接拋出中斷異常,由上層調用者處理中斷。源碼細微差別在於鎖獲取這部分代碼,這個方法與acquireQueue差別在於方法的返回途徑有兩種,一種是for循環結束,正常獲取到鎖;另一種是線程被喚醒后檢測到中斷請求,則立即拋出中斷異常,該操作導致方法結束。
/**
* Acquires in exclusive interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next =null; // help GC
failed =false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
}finally {
if (failed)
cancelAcquire(node);
}
}
結論:ReentrantLock的中斷和非中斷加鎖模式的區別在於:線程嘗試獲取鎖操作失敗后,在等待過程中,如果該線程被其他線程中斷了,它是如何響應中斷請求的。lock方法會忽略中斷請求,繼續獲取鎖直到成功;而lockInterruptibly則直接拋出中斷異常來立即響應中斷,由上層調用者處理中斷。
那么,為什么要分為這兩種模式呢?這兩種加鎖方式分別適用於什么場合呢?根據它們的實現語義來理解,我認為lock()適用於鎖獲取操作不受中斷影響的情況,此時可以忽略中斷請求正常執行加鎖操作,因為該操作僅僅記錄了中斷狀態(通過Thread.currentThread().interrupt()操作,只是恢復了中斷狀態為true,並沒有對中斷進行響應)。如果要求被中斷線程不能參與鎖的競爭操作,則此時應該使用lockInterruptibly方法,一旦檢測到中斷請求,立即返回不再參與鎖的競爭並且取消鎖獲取操作(即finally中的cancelAcquire操作)
