Guava监视器之Monitor

前言:对于一个控制锁的业务场景来说,有简单的也有复杂的,最简单的就是判断一个对象是否是null。再复杂点就是对于一个复杂条件的判断。
判断的话如果是一个boolean类型,guava提供了一个监视器类来实现,
相比传统java提供的ReentrantLock,synchronized,他提供了很大的便利性。好,我们一探窥见。

1、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语句的条件出现,以确保当前线程干净地离开监视器:

    // 实现就是包装了重入锁的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比较

下面的例子显示使用表达一个简单的线程持有人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();
       }
     }
   }

2、Monitor原理

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

从上面结构可以看出来,Monitor也有公平非公平之分,因为他底层也是基于lock封装的,比较创新
的是有个activeGuards的Guard,那么得再仔细了解下Guard类。

  • Guard类结构
final Monitor monitor;
final Condition condition;
int waiterCount = 0;
Guard next;

public abstract boolean isSatisfied();

警卫类是依赖一个monitor,没有monitor也就没有必要警卫了。
condition的作用就是关联一个锁条件,锁条件的实现是重写抽象方法isSatisfied。
waiterCount,意思是重入的次数,其实就是想知道是第一次还是最后一次,最后一次需要替换next指针。

结构看明白了,那么进入正题,看下如何做到加锁和写锁。

  • 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 发布
禁止未经授权转载,违者依法追究相关法律责任
如需授权可联系:zhuyunhui@yuanbiguo.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容