Guava監(jiān)視器之Monitor

前言:對(duì)于一個(gè)控制鎖的業(yè)務(wù)場(chǎng)景來(lái)說(shuō),有簡(jiǎn)單的也有復(fù)雜的,最簡(jiǎn)單的就是判斷一個(gè)對(duì)象是否是null差导。再?gòu)?fù)雜點(diǎn)就是對(duì)于一個(gè)復(fù)雜條件的判斷被啼。
判斷的話如果是一個(gè)boolean類型帜消,guava提供了一個(gè)監(jiān)視器類來(lái)實(shí)現(xiàn),
相比傳統(tǒng)java提供的ReentrantLock,synchronized,他提供了很大的便利性浓体。好泡挺,我們一探窺見。

1命浴、Monitor介紹

此類旨在代替ReentrantLock娄猫。與使用的代碼相比,使用的代碼Monitor 不易出錯(cuò)且可讀性強(qiáng)ReentrantLock生闲,而不會(huì)造成明顯的性能損失媳溺。
Monitor通過(guò)優(yōu)化條件的評(píng)估和信號(hào)傳遞,甚至具有提高性能的潛力碍讯。信令是完全 隱式的悬蔽。通過(guò)消除顯式的信號(hào)傳遞,
此類可以保證在條件變?yōu)檎鏁r(shí)不會(huì)喚醒一個(gè)線程(不會(huì)由于使用引起“信號(hào)風(fēng)暴” Condition.signalAll)捉兴,
并且不會(huì)丟失信號(hào)(由于對(duì)的不正確使用而不會(huì)導(dǎo)致“掛起” Condition.signal)蝎困。
在調(diào)用任何具有void返回類型的enter方法時(shí),應(yīng)始終緊隨其后的是try / finally塊倍啥,以確保當(dāng)前線程干凈地離開監(jiān)視器:


   // 實(shí)現(xiàn)就是包裝了重入鎖的lock.lock()
   monitor.enter();
   try {
     // do things while occupying the monitor
   } finally {
     monitor.leave();
   }

對(duì)任何帶有boolean返回類型的enter方法的調(diào)用應(yīng)始終作為包含try / finally塊的if語(yǔ)句的條件出現(xiàn)禾乘,以確保當(dāng)前線程干凈地離開監(jiān)視器:

    // 實(shí)現(xiàn)就是包裝了重入鎖的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
   }

1、與synchronized逗栽、ReentrantLock比較

下面的例子顯示使用表達(dá)一個(gè)簡(jiǎn)單的線程持有人synchronized盖袭, ReentrantLock和Monitor。

  • synchronized

該版本是最少的代碼行,主要是因?yàn)樗褂玫耐綑C(jī)制已內(nèi)置在語(yǔ)言和運(yùn)行時(shí)中鳄虱。但是程序員必須記住要避免幾個(gè)常見的錯(cuò)誤:wait()必須在while而不是if弟塞,并且 notifyAll()必須使用,notify()因?yàn)楸仨毜却齼蓚€(gè)不同的邏輯條件拙已。


   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版本更為冗長(zhǎng)决记,并且仍然需要程序員記住要使用while而不是if。但是倍踪,一個(gè)優(yōu)點(diǎn)是我們可以引入兩個(gè)單獨(dú)的Condition對(duì)象系宫,這使我們可以使用signal()代替signalAll(),這可能會(huì)帶來(lái)性能上的好處建车。


   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對(duì)象周圍添加了一些詳細(xì)信息扩借,但從get和set方法中刪除了相同的詳細(xì)信息,甚至更多缤至。
Monitor實(shí)現(xiàn)了與上述ReentrantLock版本中手動(dòng)編碼相同的有效信令潮罪。
最后,程序員不再需要手動(dòng)編寫等待循環(huán)的代碼领斥,因此不必記住要使用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();
       }
     }
   }

2、Monitor原理

  • 首先得了解下Monitor結(jié)構(gòu)
private final boolean fair;
private final ReentrantLock lock;
private Guard activeGuards = null;

從上面結(jié)構(gòu)可以看出來(lái)月洛,Monitor也有公平非公平之分何恶,因?yàn)樗讓右彩腔趌ock封裝的,比較創(chuàng)新
的是有個(gè)activeGuards的Guard嚼黔,那么得再仔細(xì)了解下Guard類细层。

  • Guard類結(jié)構(gòu)
final Monitor monitor;
final Condition condition;
int waiterCount = 0;
Guard next;

public abstract boolean isSatisfied();

警衛(wèi)類是依賴一個(gè)monitor,沒(méi)有monitor也就沒(méi)有必要警衛(wèi)了隔崎。
condition的作用就是關(guān)聯(lián)一個(gè)鎖條件今艺,鎖條件的實(shí)現(xiàn)是重寫抽象方法isSatisfied韵丑。
waiterCount爵卒,意思是重入的次數(shù),其實(shí)就是想知道是第一次還是最后一次撵彻,最后一次需要替換next指針钓株。

結(jié)構(gòu)看明白了,那么進(jìn)入正題陌僵,看下如何做到加鎖和寫鎖轴合。

  • Monitor加鎖

已enterWhen為例:

public void enterWhen(Guard guard) throws InterruptedException {
    // null判斷,沒(méi)什么好說(shuō)的
    if (guard.monitor != this) {
      throw new IllegalMonitorStateException();
    }
    // 減少指針引用路徑
    final ReentrantLock lock = this.lock;
    // 鎖是否被當(dāng)前線程持有
    boolean signalBeforeWaiting = lock.isHeldByCurrentThread();
    // 嘗試獲取鎖
    lock.lockInterruptibly();

    boolean satisfied = false;
    try {
        // 警衛(wèi)是否安全碗短,不安全則等待
      if (!guard.isSatisfied()) {
        // 等待警衛(wèi)通知
        await(guard, signalBeforeWaiting);
      }
      satisfied = true;
    } finally {
      if (!satisfied) {
        leave();
      }
    }
  }

  private void await(Guard guard, boolean signalBeforeWaiting) throws InterruptedException {
    // 等待是否先通知受葛,當(dāng)前線程已經(jīng)拿到鎖了,進(jìn)行看下一個(gè)等待對(duì)象
    if (signalBeforeWaiting) {
      signalNextWaiter();
    }
    // 第一次開始等待,就是記錄下waiterCount
    beginWaitingFor(guard);
    try {
      do {
        // 第一次開始await
        guard.condition.await();
        // 看條件总滩,其實(shí)和那種最普通的寫法是一樣的
      } while (!guard.isSatisfied());
    } finally {
      // 記錄下waiterCount纲堵,判斷是否需要執(zhí)行next警衛(wèi)
      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解鎖

解鎖相對(duì)加鎖步驟少了很多,finally里面進(jìn)行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
    }
  }

寫在最后

這里就簡(jiǎn)單分析下Monitor的實(shí)現(xiàn)了闰渔,點(diǎn)到為止席函,可以看出通過(guò)抽象Monitor和Guard,把鎖條件進(jìn)行封裝冈涧,有點(diǎn)策略和單個(gè)責(zé)任鏈模式的意思茂附,
這么想可能是google程序員覺(jué)得jdk的lock還是不夠抽象,所以再封裝了一層督弓。

寫這篇文章也就花了半個(gè)多小時(shí)的時(shí)間营曼,發(fā)現(xiàn)3篇文章一寫確實(shí)越來(lái)越順了,也有可能分析的還是過(guò)于表面愚隧,但是確實(shí)寫完比看完一個(gè)東西能理解更深入溶推。

這里感覺(jué)有個(gè)學(xué)習(xí)深度的總結(jié)還真有道理。

知識(shí)學(xué)習(xí)的層次是:看懂 < 說(shuō)出來(lái) < 寫出來(lái)并能讓別人也懂

本文由猿必過(guò) YBG 發(fā)布
禁止未經(jīng)授權(quán)轉(zhuǎn)載奸攻,違者依法追究相關(guān)法律責(zé)任
如需授權(quán)可聯(lián)系:zhuyunhui@yuanbiguo.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒜危,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子睹耐,更是在濱河造成了極大的恐慌辐赞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硝训,死亡現(xiàn)場(chǎng)離奇詭異响委,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)窖梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門赘风,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人纵刘,你說(shuō)我怎么就攤上這事邀窃。” “怎么了假哎?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵瞬捕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我舵抹,道長(zhǎng)肪虎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任惧蛹,我火速辦了婚禮扇救,結(jié)果婚禮上刑枝,老公的妹妹穿的比我還像新娘。我一直安慰自己迅腔,他們只是感情好仅讽,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钾挟,像睡著了一般洁灵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掺出,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天徽千,我揣著相機(jī)與錄音,去河邊找鬼汤锨。 笑死双抽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闲礼。 我是一名探鬼主播牍汹,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柬泽!你這毒婦竟也來(lái)了慎菲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锨并,失蹤者是張志新(化名)和其女友劉穎露该,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第煮,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡解幼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了包警。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撵摆。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖害晦,靈堂內(nèi)的尸體忽然破棺而出特铝,到底是詐尸還是另有隱情,我是刑警寧澤篱瞎,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布苟呐,位于F島的核電站,受9級(jí)特大地震影響俐筋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜严衬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一澄者、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦粱挡、人聲如沸赠幕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榕堰。三九已至,卻和暖如春嫌套,著一層夾襖步出監(jiān)牢的瞬間逆屡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工踱讨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留魏蔗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓痹筛,卻偏偏與公主長(zhǎng)得像莺治,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帚稠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 異常解析 在線程中調(diào)用wait方法的時(shí)候要用synchronized鎖住對(duì)象谣旁,確保代碼段不會(huì)被多個(gè)線程調(diào)用。 如果...
    光劍書架上的書閱讀 1,584評(píng)論 0 5
  • 多線程 Java多線程并發(fā) 1.1 JAVA 并發(fā)知識(shí)庫(kù) 1.2 JAVA 線程實(shí)現(xiàn)/創(chuàng)建方式 1.2.1 繼承 ...
    isuntong閱讀 209評(píng)論 0 0
  • 樂(lè)觀鎖認(rèn)為讀多寫少滋早,遇到并發(fā)的可能性低蔓挖,拿數(shù)據(jù)的時(shí)候認(rèn)為別人不會(huì)修改,所以不上鎖馆衔,但是更新數(shù)據(jù)時(shí)瘟判,會(huì)判斷一下有沒(méi)有...
    繁星追逐閱讀 384評(píng)論 0 0
  • 簡(jiǎn)書 賈小強(qiáng)轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝角溃! 在面試中你可能遇到過(guò)這樣的問(wèn)題:鎖(lock)和監(jiān)視器(monitor)有...
    賈小強(qiáng)閱讀 3,905評(píng)論 1 3
  • 線程與進(jìn)程的區(qū)別拷获? 進(jìn)程是操作系統(tǒng)分配資源的最小單元,線程是操作系統(tǒng)調(diào)度的最小單元减细。一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)...
    奇點(diǎn)一氪閱讀 380評(píng)論 0 1