前言:对于一个管制锁的业务场景来说,有简略的也有简单的,最简略的就是判断一个对象是否是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 公布