該系列文章收錄在公衆號【Ccww技術博客】,原創技術文章早於博客推出
在面試,不少時間面試官都會問到鎖的問題,ReentrantLock也是常問一個點,但具體會問什麼呢?在網上收集到一些問題:java
ReentrantLock::lock公平鎖模式現實node
那麼重入鎖是什麼?有什麼用呢面試
ReentrantLock是個典型的獨佔模式AQS,同步狀態爲0時表示空閒。當有線程獲取到空閒的同步狀態時,它會將同步狀態加1,將同步狀態改成非空閒,因而其餘線程掛起等待。在修改同步狀態的同時,並記錄下本身的線程,做爲後續重入的依據,即一個線程持有某個對象的鎖時,再次去獲取這個對象的鎖是能夠成功的。若是是不可重入的鎖的話,就會形成死鎖。安全
ReentrantLock會涉及到公平鎖和非公平鎖,實現關鍵在於成員變量sync
的實現不一樣,這是鎖實現互斥同步的核心。函數
//公平鎖和非公平鎖的變量 private final Sync sync; //父類 abstract static class Sync extends AbstractQueuedSynchronizer {} //公平鎖子類 static final class FairSync extends Sync {} //非公平鎖子類 static final class NonfairSync extends Sync {}
那公平鎖和非公平鎖是什麼?有什麼區別?性能
公平鎖是指當鎖可用時,在鎖上等待時間最長的線程將得到鎖的使用權,即先進先出。而非公平鎖則隨機分配這種使用權,是一種搶佔機制,是隨機得到鎖,並非先來的必定能先獲得鎖。ui
ReentrantLock提供了一個構造方法,能夠實現公平鎖或非公平鎖:spa
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
雖然公平鎖在公平性得以保障,但由於公平的獲取鎖沒有考慮到操做系統對線程的調度因素以及其餘因素,會影響性能。操作系統
雖然非公平模式效率比較高,可是非公平模式在申請獲取鎖的線程足夠多,那麼可能會形成某些線程長時間得不到鎖,這就是非公平鎖的「飢餓」問題。線程
但大部分狀況下咱們使用非公平鎖,由於其性能比公平鎖好不少。可是公平鎖可以避免線程飢餓,某些狀況下也頗有用。
接下來看看ReentrantLock公平鎖的實現:
首先須要在構建函數中傳入true
建立好公平鎖
ReentrantLock reentrantLock = new ReentrantLock(true);
調用lock()
進行上鎖,直接acquire(1)
上鎖
public void lock() { // 調用的sync的子類FairSync的lock()方法:ReentrantLock.FairSync.lock() sync.lock(); } final void lock() { // 調用AQS的acquire()方法獲取鎖,傳的值爲1 acquire(1); }
直接嘗試獲取鎖,
// AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { // 嘗試獲取鎖 // 若是失敗了,就排隊 if (!tryAcquire(arg) && // 注意addWaiter()這裏傳入的節點模式爲獨佔模式 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
具體獲取鎖流程
getState()
獲取同步狀態state
值,進行判斷是否爲0:
state+1
// ReentrantLock.FairSync.tryAcquire() protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 狀態變量的值爲0,說明暫時尚未線程佔有鎖 if (c == 0) { // hasQueuedPredecessors()保證了不管是新的線程仍是已經排隊的線程都順序使用鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 當前線程獲取了鎖,並將本線程設置到exclusiveOwnerThread變量中, //供後續本身可重入獲取鎖做準備 setExclusiveOwnerThread(current); return true; } } // 之因此說是重入鎖,就是由於在獲取鎖失敗的狀況下,還會再次判斷是否當前線程已經持有鎖了 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 設置到state中 // 由於當前線程佔有着鎖,其它線程只會CAS把state從0更新成1,是不會成功的 // 因此不存在競爭,天然不須要使用CAS來更新 setState(nextc); return true; } return false; }
若是獲取失敗加入隊列裏,那具體怎麼處理呢?經過自旋的方式,隊列中線程不斷進行嘗試獲取鎖操做,中間是能夠經過中斷的方式打斷,
若是當前節點的前一個節點爲head節點,則說明輪到本身獲取鎖了,調用tryAcquire()
方法再次嘗試獲取鎖
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋 for (;;) { // 當前節點的前一個節點, final Node p = node.predecessor(); // 若是當前節點的前一個節點爲head節點,則說明輪到本身獲取鎖了 // 調用ReentrantLock.FairSync.tryAcquire()方法再次嘗試獲取鎖 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的上一個節點不是Head,是須要判斷是否須要阻塞,以及尋找安全點掛起。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 上一個節點的等待狀態 int ws = pred.waitStatus; // 等待狀態爲SIGNAL(等待喚醒),直接返回true if (ws == Node.SIGNAL) return true; // 前一個節點的狀態大於0,已取消狀態 if (ws > 0) { // 把前面全部取消狀態的節點都從鏈表中刪除 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前一個Node的狀態小於等於0,則把其狀態設置爲等待喚醒 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
在看完獲取鎖的流程,那麼你知道ReentrantLock如何實現公平鎖了嗎?其實就是在tryAcquire()
的實現中。
在tryAcquire()
的實現中使用了hasQueuedPredecessors()
保證了線程先進先出FIFO的使用鎖,不會產生"飢餓"問題,
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 狀態變量的值爲0,說明暫時尚未線程佔有鎖 if (c == 0) { // hasQueuedPredecessors()保證了不管是新的線程仍是已經排隊的線程都順序使用鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { .... } ... } } public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
tryAcquire都會檢查CLH隊列中是否仍有前驅的元素,若是仍然有那麼繼續等待,經過這種方式來保證先來先服務的原則。
那這樣ReentrantLock如何實現可重入?是怎麼重入的?
其實也很簡單,在獲取鎖後,設置一個標識變量爲當前線程exclusiveOwnerThread
,當線程再次進入判斷exclusiveOwnerThread
變量是否等於本線程來判斷.
protected final boolean tryAcquire(int acquires) { // 狀態變量的值爲0,說明暫時尚未線程佔有鎖 if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 當前線程獲取了鎖,並將本線程設置到exclusiveOwnerThread變量中, //供後續本身可重入獲取鎖做準備 setExclusiveOwnerThread(current); return true; } } //之因此說是重入鎖,就是由於在獲取鎖失敗的狀況下,還會再次判斷是否當前線程已經持有鎖了 else if (current == getExclusiveOwnerThread()) { ... } }
當看完公平鎖獲取鎖的流程,那其實咱們也瞭解非公平鎖獲取鎖,那咱們來看看。
其實非公平鎖獲取鎖獲取區別主要在於:
false
或者爲null,爲建立非公平鎖NonfairSync
,true
建立公平鎖,非公平鎖在獲取鎖的時候,先去檢查state
狀態,再直接執行aqcuire(1)
,這樣能夠提升效率,
final void lock() { if (compareAndSetState(0, 1)) //修改同步狀態的值成功的話,設置當前線程爲獨佔的線程 setExclusiveOwnerThread(Thread.currentThread()); else //獲取鎖 acquire(1); }
tryAcquire()
中沒有hasQueuedPredecessors()
保證了不管是新的線程仍是已經排隊的線程都順序使用鎖。其餘功能都相似。在理解了獲取鎖下,咱們更好理解ReentrantLock::unlock()鎖的釋放,也比較簡單。
釋放當前線程佔用的鎖
protected final boolean tryRelease(int releases) { // 計算釋放後state值 int c = getState() - releases; // 若是不是當前線程佔用鎖,那麼拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 鎖被重入次數爲0,表示釋放成功 free = true; // 清空獨佔線程 setExclusiveOwnerThread(null); } // 更新state值 setState(c); return free; }
若釋放成功,就須要喚醒等待隊列中的線程,先查看頭結點的狀態是否爲SIGNAL,若是是則喚醒頭結點的下個節點關聯的線程,若是釋放失敗那麼返回false表示解鎖失敗。
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next;//這裏的s是頭節點(如今是頭節點持有鎖)的下一個節點,也就是指望喚醒的節點 if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); //喚醒s表明的線程 }
綜合上面的ReentrantLock的可重入,可實現公平非公平鎖的特性外,還具備哪些特性?
interrupted
,並不會對運行中的線程有什麼影響,具體須要根據這個中斷標誌幹些什麼,用戶本身去決定。好比,實現了等待鎖的時候,5秒沒有獲取到鎖,中斷等待,線程繼續作其它事情。ReetrantLock::tryLock(long timeout, TimeUnit unit)
提供了超時獲取鎖的功能。它的語義是在指定的時間內若是獲取到鎖就返回true,獲取不到則返回false。這種機制避免了線程無限期的等待鎖釋放。各位看官還能夠嗎?喜歡的話,動動手指點個💗,點個關注唄!!謝謝支持!
歡迎關注公衆號【Ccww技術博客】,原創技術文章第一時間推出