共计 5156 个字符,预计需要花费 13 分钟才能阅读完成。
前言:对于一个管制锁的业务场景来说,有简略的也有简单的,最简略的就是判断一个对象是否是 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 公布