我們知道實現一把鎖要有如下幾個邏輯:
我們在講解AQS的時候說過AQS基本負責了實現鎖的全部邏輯,唯獨線程搶鎖和線程釋放鎖的邏輯是交給子類來實現了,而ReentrantLock作為最常用的獨占鎖,其內部就是包含了AQS的子類實現了線程搶鎖和釋放鎖的邏輯。
我們在使用ReentrantLock的時候一般只會使用如下方法:
ReentrantLock lock=new ReentrantLock(); lock.lock(); lock.unlock(); lock.tryLock(); Condition condition=lock.newCondition(); condition.await(); condition.signal(); condition.signalAll();
如果我們自己來實現一個鎖,那么如何設計呢?
根據AQS的邏輯,我們寫一個子類sync,這個類一定會調用父類的acquire方法進行上鎖,同時重寫tryAcquire方法實現自己搶鎖邏輯,也一定會調用release方法進行解鎖,同時重寫tryRelease方法實現釋放鎖邏輯。
那么ReentrantLock是怎么實現的呢?
ReentrantLock的實現的類架構如下,ReentrantLock對外提供作為一把鎖應該具備的api,比如lock加鎖,unlock解鎖等等,而它內部真正的實現是通過靜態內部類sync實現,sync是AQS的子類,是真正的鎖,因為這把鎖需要支持公平和非公平的特性,所以sync又有兩個子類FairSync和NonfairSync分別實現公平鎖和非公平鎖。
因為是否公平說的是搶鎖的時候是否公平,那兩個子類就要在上鎖方法acquire的調用和搶鎖方法tryAcquire的重寫上做文章。
公平鎖做了什么文章?
static final class FairSync extends Sync { final void lock() { acquire(1); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && 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; } }
公平鎖比較簡單,直接調用了父級類AQS的acquire方法,因為AQS的鎖默認就是公平的排隊策略。
重寫tryAcquire方法的邏輯為:
公平就公平在老老實實排隊。
非公平鎖做了什么文章?
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //nonfairTryAcquire代碼在父類sync里面 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; }
非公平鎖也很簡單,沒有直接調用了父級類AQS的acquire方法,而是先通過cas搶鎖,它不管等待隊列中有沒有其他線程在排隊,直接搶鎖,這就體現了不公平。
它重寫tryAcquire方法的邏輯為:
公平鎖和非公平分別重寫了tryAcquire方法,來滿足公平和非公平的特性。那么tryAcquire方法也是需要子類重寫的,因為它和是否公平無關,因此tryAcquire方法被抽象到sync類中重寫。
sync類中protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
釋放鎖的邏輯如下:
釋放鎖往往和搶鎖邏輯是對應的,每個子類搶鎖邏輯不同的話,釋放鎖的邏輯也會對應不同。
接下來我們通過ReentrantLock的使用看下它的源碼實現:
class X { private final ReentrantLock lock = new ReentrantLock(); Condition condition1=lock.newCondition(); Condition condition2=lock.newCondition(); public void m() { lock.lock(); try { if(條件1){ condition1.await(); } if(條件2){ condition2.await(); } } catch (InterruptedException e) { } finally { condition1.signal(); condition2.signal(); lock.unlock(); } } }
ReentrantLock類public void lock() { sync.lock(); }
NonfairSync 類中 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
FairSync 類中 final void lock() { acquire(1); }
公平鎖和非公平鎖中都實現了lock方法,公平鎖直接調用AQS的acquire,而非公平鎖先搶鎖,搶不到鎖再調用AQS的acquire方法進行上鎖
進入acquire方法后的邏輯我們就都知道了。
public void unlock() { sync.release(1);}
unlock方法內直接調用了AQS的Release方法進行解鎖的邏輯,進入release方法后邏輯我們都已經知道了,這里不再往下跟。
這個方法中直接調用了sync中的nonfairTryAcquire方法,這個就是非公平鎖實現的TryAcquire方法,只是名字改了而已。
tryLock方法的意思是嘗試搶鎖,是給程序員調用的嘗試搶鎖方法,如果搶鎖成功返回true,搶鎖失敗返回false,當拿到搶鎖失敗的信號后,程序員可以做一些自己的策略。
如果沒有tryLock方法,而是直接調用lock方法,就會走到acquire方法中,Acquire方法中也會調用TryAcquire方法,只不過在這里如果獲取不到鎖就會入隊阻塞了,就沒有程序員參與的可能了。
在AQS那篇我們說過Condition是AQS中的條件隊列,可以按條件將一批線程由不可喚醒變為可喚醒。
ReentrantLock類 public Condition newCondition() { return sync.newCondition(); }
sync靜態內部類final ConditionObject newCondition() { return new ConditionObject();}
sync提供了創建Condition對象的方法,意味著ReentrantLock也擁有Condition的能力。
我們下面說的ReentrantLock其實就是說AQS,因為它的同步實現主要在AQS里面。
優化前的synchronized性能很差,主要表現在兩個方面:
ReentrantLock是在jdk實現的,它申請互斥量就是對鎖標識state的爭奪,它是通過cas方式實現。在java端實現。
對于爭奪不到資源的線程依然要阻塞掛起,但凡阻塞掛起都要依賴于操作系統底層,這一步的用戶態到內核態的切換是避免不了的。
因此在單線程進入代碼塊的時候,效率是很高的,因此我們說ReentrantLock性能高于原始的synchronized:
兩個都是常用的典型的獨占鎖。
synchronized不需要手動釋放鎖,ReentrantLock需要手動釋放鎖,需要考慮異常對釋放鎖的影響避免異常導致線程一直持有鎖。
以下是兩個鎖的使用方式:
class X { private final ReentrantLock lock = new ReentrantLock(); Condition condition1=lock.newCondition(); Condition condition2=lock.newCondition(); public void m() { lock.lock(); try { if(1==2){ condition1.await(); } if(1==3){ condition2.await(); } } catch (InterruptedException e) { } finally { condition1.signal(); condition2.signal(); lock.unlock(); } } }
class X { private final testtest sync=new testtest();; public void m() throws InterruptedException { synchronized(sync){ if(1==2){ sync.wait(); } sync.notify(); sync.notifyAll(); } } }
對比代碼及特性說明:
本文鏈接:http://www.tebozhan.com/showinfo-26-15183-0.html被問到ReentrantLock你真的能答好嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 是時候放棄Dockerfile了,考慮上手Buildpack吧
下一篇: 掌握Go編程中的錯誤處理和日志記錄