Java並發(九):重入鎖 ReentrantLock
一、ReentrantLock類結構
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; // 鎖 大部分功能都是委托給Sync來實現的 abstract static class Sync extends AbstractQueuedSynchronizer {} static final class FairSync extends Sync {} static final class NonfairSync extends Sync {} }
二、以NonfairSync為例解析重入鎖
獲取鎖標誌:
(NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer)
1.AbstractQueuedSynchronizer.state>0(0代表沒有被占用,大於0代表有線程持有當前鎖(鎖可以重入,每次重入都+1))
2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread()
獲取鎖:
public staticvoid main(String[] args) { ReentrantLock lock = new ReentrantLock();// 默認是非公平鎖 lock.lock(); } // ReentrantLock public void lock() { sync.lock(); } // NonfairSync final void lock() { if (compareAndSetState(0, 1)) // 嘗試獲取鎖 setExclusiveOwnerThread(Thread.currentThread()); //如果拿到鎖就設置當前線程 else acquire(1); } // AbstractQueuedSynchronizer public final void acquire(int arg) { if (!tryAcquire(arg) && // 嘗試獲取鎖 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 沒有獲取到鎖,將線程加入同步隊列(參考上一篇AbstractQueuedSynchronizer) selfInterrupt(); } // NonfairSync protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } /** * 獲取鎖標誌: * (NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer) * 1.AbstractQueuedSynchronizer.state>0(0代表沒有被占用,大於0代表有線程持有當前鎖(鎖可以重入,每次重入都+1)) * 2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread() * Sync(NonfairSync沒有重寫nonfairTryAcquire) */ 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; }
釋放鎖:
// ReentrantLock public void unlock() { sync.release(1); } // AbstractQueuedSynchronizer public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 喚醒隊列下一個節點線程 參考上一篇:AbstractQueuedSynchronizer return true; } return false; } // Sync protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 重入鎖,直到state==0才算釋放 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
三、公平鎖與非公平鎖
// FairSync(NonfairSync會先嘗試拿鎖,FairSync不會) final void lock() { acquire(1); } // FairSync protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // CLH隊列為空或者隊列頭結點是當前線程節點 才能獲得鎖 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; } /** * AbstractQueuedSynchronizer * true - CLH隊列為空或者隊列頭結點是當前線程節點 */ public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
以上代碼可以看出,公平鎖和非公平鎖只有兩處不同:
(1)非公平鎖在調用 lock 後,首先就會調用 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被占用,那麽直接就獲取到鎖。
(2)非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法。在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,而公平鎖會判斷等待隊列是否有線程處於等待狀態,如果有則不去搶鎖,乖乖排到後面。
公平鎖和非公平鎖就這兩點區別,如果這兩次 CAS 都不成功,那麽後面非公平鎖和公平鎖是一樣的,都要進入到阻塞隊列等待喚醒。
因此,非公平鎖會有更好的性能,因為它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞隊列中的線程長期處於饑餓狀態。
四、ReentrantLock優勢
ReentrantLock與synchronized具有相同的功能和內存語義。
1、與synchronized相比,ReentrantLock提供了更多,更加全面的功能,具備更強的擴展性。例如:時間鎖等候,可中斷鎖等候,鎖投票。
2、ReentrantLock還提供了條件Condition,對線程的等待、喚醒操作更加詳細和靈活,所以在多個條件變量和高度競爭鎖的地方,ReentrantLock更加適合。
3、ReentrantLock提供了可輪詢的鎖請求。它會嘗試著去獲取鎖,如果成功則繼續,否則可以等到下次運行時處理,而synchronized則一旦進入鎖請求要麽成功要麽阻塞,所以相比synchronized而言,ReentrantLock會不容易產生死鎖些。
4、ReentrantLock支持更加靈活的同步代碼塊,但是使用synchronized時,只能在同一個synchronized塊結構中獲取和釋放。註:ReentrantLock的鎖釋放一定要在finally中處理,否則可能會產生嚴重的後果。
5、ReentrantLock支持中斷處理,且性能較synchronized會好些。
參考資料 / 相關推薦
【死磕Java並發】—–J.U.C之重入鎖:ReentrantLock
一行一行源碼分析清楚AbstractQueuedSynchronizer
Java並發(八):AbstractQueuedSynchronizer
Java並發(九):重入鎖 ReentrantLock