前言:對於一個控制鎖的業務場景來講,有簡單的也有複雜的,最簡單的就是判斷一個對象是不是null。再複雜點就是對於一個複雜條件的判斷。
判斷的話若是是一個boolean類型,guava提供了一個監視器類來實現,
相比傳統java提供的ReentrantLock,synchronized,他提供了很大的便利性。好,咱們一探窺見。java
此類旨在代替ReentrantLock。與使用的代碼相比,使用的代碼Monitor 不易出錯且可讀性強ReentrantLock,而不會形成明顯的性能損失。
Monitor經過優化條件的評估和信號傳遞,甚至具備提升性能的潛力。信令是徹底 隱式的。經過消除顯式的信號傳遞,
此類能夠保證在條件變爲真時不會喚醒一個線程(不會因爲使用引發「信號風暴」 Condition.signalAll),
而且不會丟失信號(因爲對的不正確使用而不會致使「掛起」 Condition.signal)。
在調用任何具備void返回類型的enter方法時,應始終緊隨其後的是try / finally塊,以確保當前線程乾淨地離開監視器:程序員
// 實現就是包裝了重入鎖的lock.lock() monitor.enter(); try { // do things while occupying the monitor } finally { monitor.leave(); }
對任何帶有boolean返回類型的enter方法的調用應始終做爲包含try / finally塊的if語句的條件出現,以確保當前線程乾淨地離開監視器:web
// 實現就是包裝了重入鎖的lock.tryLock() if (monitor.tryEnter()) { try { // do things while occupying the monitor } finally { monitor.leave(); } } else { // do other things since the monitor was not available }
下面的例子顯示使用表達一個簡單的線程持有人synchronized, ReentrantLock和Monitor。安全
該版本是最少的代碼行,主要是由於所使用的同步機制已內置在語言和運行時中。可是程序員必須記住要避免幾個常見的錯誤:wait()必須在while而不是if,而且 notifyAll()必須使用,notify()由於必須等待兩個不一樣的邏輯條件。性能
public class SafeBox<V> { private V value; public synchronized V get() throws InterruptedException { while (value == null) { wait(); } V result = value; value = null; notifyAll(); return result; } public synchronized void set(V newValue) throws InterruptedException { while (value != null) { wait(); } value = newValue; notifyAll(); } }
該版本比synchronized版本更爲冗長,而且仍然須要程序員記住要使用while而不是if。可是,一個優勢是咱們能夠引入兩個單獨的Condition對象,這使咱們可使用signal()代替signalAll(),這可能會帶來性能上的好處。學習
public class SafeBox<V> { private final ReentrantLock lock = new ReentrantLock(); private final Condition valuePresent = lock.newCondition(); private final Condition valueAbsent = lock.newCondition(); private V value; public V get() throws InterruptedException { lock.lock(); try { while (value == null) { valuePresent.await(); } V result = value; value = null; valueAbsent.signal(); return result; } finally { lock.unlock(); } } public void set(V newValue) throws InterruptedException { lock.lock(); try { while (value != null) { valueAbsent.await(); } value = newValue; valuePresent.signal(); } finally { lock.unlock(); } } }
此版本在Guard對象周圍添加了一些詳細信息,但從get和set方法中刪除了相同的詳細信息,甚至更多。
Monitor實現了與上述ReentrantLock版本中手動編碼相同的有效信令。
最後,程序員再也不須要手動編寫等待循環的代碼,所以沒必要記住要使用while代替if。優化
public class SafeBox<V> { private final Monitor monitor = new Monitor(); private final Monitor.Guard valuePresent = new Monitor.Guard(monitor) { public boolean isSatisfied() { return value != null; } }; private final Monitor.Guard valueAbsent = new Monitor.Guard(monitor) { public boolean isSatisfied() { return value == null; } }; private V value; public V get() throws InterruptedException { monitor.enterWhen(valuePresent); try { V result = value; value = null; return result; } finally { monitor.leave(); } } public void set(V newValue) throws InterruptedException { monitor.enterWhen(valueAbsent); try { value = newValue; } finally { monitor.leave(); } } }
private final boolean fair; private final ReentrantLock lock; private Guard activeGuards = null;
從上面結構能夠看出來,Monitor也有公平非公平之分,由於他底層也是基於lock封裝的,比較創新
的是有個activeGuards的Guard,那麼得再仔細瞭解下Guard類。this
final Monitor monitor; final Condition condition; int waiterCount = 0; Guard next; public abstract boolean isSatisfied();
警衛類是依賴一個monitor,沒有monitor也就沒有必要警衛了。
condition的做用就是關聯一個鎖條件,鎖條件的實現是重寫抽象方法isSatisfied。
waiterCount,意思是重入的次數,其實就是想知道是第一次仍是最後一次,最後一次須要替換next指針。google
結構看明白了,那麼進入正題,看下如何作到加鎖和寫鎖。編碼
已enterWhen爲例:
public void enterWhen(Guard guard) throws InterruptedException { // null判斷,沒什麼好說的 if (guard.monitor != this) { throw new IllegalMonitorStateException(); } // 減小指針引用路徑 final ReentrantLock lock = this.lock; // 鎖是否被當前線程持有 boolean signalBeforeWaiting = lock.isHeldByCurrentThread(); // 嘗試獲取鎖 lock.lockInterruptibly(); boolean satisfied = false; try { // 警衛是否安全,不安全則等待 if (!guard.isSatisfied()) { // 等待警衛通知 await(guard, signalBeforeWaiting); } satisfied = true; } finally { if (!satisfied) { leave(); } } } private void await(Guard guard, boolean signalBeforeWaiting) throws InterruptedException { // 等待是否先通知,當前線程已經拿到鎖了,進行看下一個等待對象 if (signalBeforeWaiting) { signalNextWaiter(); } // 第一次開始等待,就是記錄下waiterCount beginWaitingFor(guard); try { do { // 第一次開始await guard.condition.await(); // 看條件,其實和那種最普通的寫法是同樣的 } while (!guard.isSatisfied()); } finally { // 記錄下waiterCount,判斷是否須要執行next警衛 endWaitingFor(guard); } } private void signalNextWaiter() { for (Guard guard = activeGuards; guard != null; guard = guard.next) { if (isSatisfied(guard)) { guard.condition.signal(); break; } } } private void beginWaitingFor(Guard guard) { int waiters = guard.waiterCount++; if (waiters == 0) { // push guard onto activeGuards guard.next = activeGuards; activeGuards = guard; } } private void endWaitingFor(Guard guard) { int waiters = --guard.waiterCount; if (waiters == 0) { // unlink guard from activeGuards for (Guard p = activeGuards, pred = null; ; pred = p, p = p.next) { if (p == guard) { if (pred == null) { activeGuards = p.next; } else { pred.next = p.next; } p.next = null; // help GC break; } } } }
解鎖相對加鎖步驟少了不少,finally裏面進行unlock釋放鎖
/** * Leaves this monitor. May be called only by a thread currently occupying this monitor. */ public void leave() { final ReentrantLock lock = this.lock; try { // No need to signal if we will still be holding the lock when we return if (lock.getHoldCount() == 1) { signalNextWaiter(); } } finally { lock.unlock(); // Will throw IllegalMonitorStateException if not held } }
這裏就簡單分析下Monitor的實現了,點到爲止,能夠看出經過抽象Monitor和Guard,把鎖條件進行封裝,有點策略和單個責任鏈模式的意思,
這麼想多是google程序員以爲jdk的lock仍是不夠抽象,因此再封裝了一層。
寫這篇文章也就花了半個多小時的時間,發現3篇文章一寫確實愈來愈順了,也有可能分析的仍是過於表面,可是確實寫完比看完一個東西能理解更深刻。
這裏感受有個學習深度的總結還真有道理。
知識學習的層次是:看懂 < 說出來 < 寫出來並能讓別人也懂
本文由猿必過 YBG 發佈