看了幾篇關於這三者區別的文章,但都說的不夠具體,自己去讀了下源碼,大概是清楚了三者的功能,不想了解源碼的可以跳到最後看總結。
首先,ReentrantLock類中使用了大量的CAS操作,也就是CompareAndSwap原子操作,依靠硬件保證互斥與同步,然後說下interrupt()方法。每個線程都有一個interrupt標誌。當線程在阻塞狀態時,如sleep、wait、await(park)、join,此時如果對該進程調用interrupt()方法,該線程會被喚醒、interrupt標誌被修改,並拋出InterruptedException異常(一旦捕捉到異常,立刻重置interrupt標誌),因此上述的那些方法被要求爲必須捕捉異常;當線程在運行中並未進入任何阻塞狀態時,則interrupt()操作不會打算線程執行,只會修改該線程interrupt標誌,此時可以通過Interrupted()/isInterrupted()查看並作出處理(前者會重置該標誌位),通常使用while循環來檢查。
一、 lock()方法
使用lock()獲取鎖,若獲取成功,標記下是該線程獲取到了鎖(用於鎖重入),然後返回。若獲取失敗,這時跑一個for循環,循環中先將線程阻塞放入等待隊列,當被調用signal()時線程被喚醒,這時進行鎖競爭(因爲默認使用的是非公平鎖),如果此時用CAS獲取到了鎖那麼就返回,如果沒獲取到那麼再次放入等待隊列,等待喚醒,如此循環。其間就算外部調用了interrupt(),循環也會繼續走下去。一直到當前線程獲取到了這個鎖,此時才處理interrupt標誌,若有,則執行 Thread.currentThread().interrupt(),結果如何取決於外層的處理。lock()最終執行的方法如下:
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; //獲取成功返回interrupted標誌
}
// 只修改標誌位,不做其他處理
if (shouldParkAfterFailedAcquire(p, node) && <strong>parkAndCheckInterrupt()</strong>)
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
其中parkAndCheckInterrupt()調用了LockSupport.park(),該方法使用Unsafe類將進程阻塞並放入等待隊列,等待喚醒,和await()有點類似。
可以看到循環中檢測到了interrupt標記,但是僅做 interrupted = true 操作,直到獲取到了鎖,才return interrupted,然後處理如下
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 執行Thread.currentThread().interrupt()
}
二、 lockInterruptibly()方法
和lock()相比,lockInterruptibly()只有略微的修改,for循環過程中,如果檢測到interrupt標誌爲true,則立刻拋出InterruptedException異常,這時程序變通過異常直接返回到最外層了,又外層繼續處理,因此使用lockInterruptibly()時必須捕捉異常。lockInterruptibly()最終執行的方法如下: 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);
}
}
三、 tryLock()方法
使用tryLock()嘗試獲取鎖,若獲取成功,標記下是該線程獲取到了鎖,然後返回true;若獲取失敗,此時直接返回false,告訴外層沒有獲取到鎖,之後的操作取決於外層,代碼如下:
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;
}
else if 中的情況屬於鎖重入情況,即外層函數已經獲取到這個鎖了,內部的函數又再次對其進行獲取,那麼對計數器加一,不再循環加鎖。還有一種情況,即使用tryLock(long timeout, TimeUnit unit)獲取鎖,此時有點像lock()和lockInterruptibly()的混合體,若獲鎖取成功,直接返回true;若獲取失敗跑一個for循環,則用 LockSupport.parkNanos(this, nanosTimeout) 阻塞進程並放入等待隊列,等待喚醒,同時timeout扣減每次循環消耗的時間,當timeout用盡時如果依然沒有獲取到鎖,那麼就返回false。對於循環期間收到的interrupt()的處理,這兒和lockInterruptibly()一樣,一旦檢測到,那麼直接拋出異常。代碼如下:
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
long lastTime = System.nanoTime();
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 true; //獲取成功返回
}
if (nanosTimeout <= 0)
return false;
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
<strong>LockSupport.parkNanos(this, nanosTimeout)</strong>; //帶timeout的阻塞
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now; // 更新timeout
if (Thread.interrupted())
throw new InterruptedException(); //直接拋出異常
}
} finally {
if (failed)
cancelAcquire(node);
}
總結
- lock() 阻塞式地獲取鎖,只有在獲取到鎖後才處理interrupt信息
- lockInterruptibly() 阻塞式地獲取鎖,立即處理interrupt信息,並拋出異常
- tryLock() 嘗試獲取鎖,不管成功失敗,都立即返回true、false
- tryLock(long timeout, TimeUnit unit)在timeout時間內阻塞式地獲取鎖,成功返回true,超時返回false,同時立即處理interrupt信息,並拋出異常
以上就是我對這三種方法的認識,第一次寫技術博客,發現還是挺耗時間的,如果有什麼錯誤還望大家請指正。