Guava監視器之Monitor

前言:對於一個控制鎖的業務場景來講,有簡單的也有複雜的,最簡單的就是判斷一個對象是不是null。再複雜點就是對於一個複雜條件的判斷。
判斷的話若是是一個boolean類型,guava提供了一個監視器類來實現,
相比傳統java提供的ReentrantLock,synchronized,他提供了很大的便利性。好,咱們一探窺見。java

一、Monitor介紹

此類旨在代替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比較

下面的例子顯示使用表達一個簡單的線程持有人synchronized, ReentrantLock和Monitor。安全

  • synchronized

該版本是最少的代碼行,主要是由於所使用的同步機制已內置在語言和運行時中。可是程序員必須記住要避免幾個常見的錯誤: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();
     }
   }
  • ReentrantLock

該版本比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();
       }
     }
   }
  • Monitor

此版本在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();
       }
     }
   }

二、Monitor原理

  • 首先得了解下Monitor結構
private final boolean fair;
private final ReentrantLock lock;
private Guard activeGuards = null;

從上面結構能夠看出來,Monitor也有公平非公平之分,由於他底層也是基於lock封裝的,比較創新
的是有個activeGuards的Guard,那麼得再仔細瞭解下Guard類。this

  • Guard類結構
final Monitor monitor;
final Condition condition;
int waiterCount = 0;
Guard next;

public abstract boolean isSatisfied();

警衛類是依賴一個monitor,沒有monitor也就沒有必要警衛了。
condition的做用就是關聯一個鎖條件,鎖條件的實現是重寫抽象方法isSatisfied。
waiterCount,意思是重入的次數,其實就是想知道是第一次仍是最後一次,最後一次須要替換next指針。google

結構看明白了,那麼進入正題,看下如何作到加鎖和寫鎖。編碼

  • Monitor加鎖

已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;
        }
      }
    }
  }
  • Monitor解鎖

解鎖相對加鎖步驟少了不少,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 發佈
相關文章
相關標籤/搜索