第14章 构建自定义的同步工具
创建状态依赖类的最简单方式通常是在类库中现有状在载赖类的基础上进行构造
状态依赖性的管理
可阻塞的状态依赖操作的结构
// acquire lock on object state
while (precondition dose not hold) {
release lock
wait until precondition might hold
optionally fail if interrupted on timeout expires
reacquire lock
}
perform action
release lock
有界缓存实现的基类
@ThreadSafe
public abstract class BaseBoundedBuffer<V> {
@GuardedBy("this")
private final V[] buf;
@GuradedBy("this")
private final int tail;
@GuardedBy("this")
private final int head;
@GuardedBy("this")
private final int count;
protected BaseBoundedBuffer(int capacity) {
this.buf = (V[])new Object[capacity];
}
protected synchronized final void doPut(V v) {
buf[tail] = v;
if (++tail == buf.length) {
tail = 0;
}
++count;
}
protected synchronized final V doTake() {
V v = buf[head];
buf[head] = null;
if (++head == buf.length) {
head = 0;
}
--count;
return v;
}
public synchronized final boolean isFull() {
return count == buf.length;
}
public synchronized final boolean isEmpty() {
return count == 0;
}
}
示例:将前提条件的失败传递给调用者
当不满足前提条件时,有界缓存不会执行相应操作
@ThreadSafe
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
public GrumpyBoundedBuffer(int size) {
super(size);
}
public synchronized void put(V v) throws BufferFullException {
if (isFull()) {
throw new BufferFullException();
}
doPut(v);
}
public synchronized V take() throws BufferEmptyException {
if (isEmpty()) {
throw new BufferEmptyException();
}
return doTake();
}
}
示例:通过轮询与休眠来实现简单的阻塞
@ThreadSafe
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
public SleepyBoundedBuffer(int size) {
super(size);
}
public void put(V v) throws InterruptedException {
while (true) {
synchronized (this) {
if (!isFull()) {
doPut(v);
return;
}
}
Thread.sleep(SLEEP_GRANULARITY);
}
}
public V take() throws InterruptedException {
while (true) {
synchronized (this) {
if (!isEmpty()) {
return doTake();
}
}
Thread.sleep(SLEEP_GRANULARITY);
}
}
}
条件队列
使用条件队列实现的有界缓存
@ThreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {
public BoundedBuffer(int size) {
super(size);
}
// 阻塞直到 not-full
public synchronized void put(V v) throws InterruptedException {
while (isFull()) {
wait();
}
doPut(v);
notfiyAll();
}
// 阻塞直到 not-empty
public synchronized V take() throws InterruptedException {
while (isEmpty()) {
wait();
}
V v = doTake();
notifyAll();
return v;
}
}
使用条件队列
条件队列使构建高效以及高可响应性的状态依赖类变得更容易, 但同时也很容易被不正确地使用。
条件谓词
关键找出对象在哪个条件谓词上等待
每次wait调用都会隐式的与特定的条件谓词关联起来。当调用某个特定条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,并且这个锁必须保持着构成条件谓词的状态变量
过早唤醒
内置条件队列可以与多个条件谓词一起使用。当一个线程由于调用 notifyAll 而醒来时,并不意味该线程正在等待的条件谓词已经变成真了。
所以,每当线程从wait中唤醒时,都必须再次测试条件谓词
void stateDependentMethod() throws InterruptedException {
synchronized (lock) {
while (!conditionPredicate()) {
lock.wait();
}
}
}
当使用条件等待时(Object.wait 或 Condition.await)
- 通常都有一个条件谓词--包括 一些对象的状态的测试,线程在执行前必须首先通过这些测试
- 在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试
- 在一个循环中调用wait
- 确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量
- 当调用wait,notify,notifyAll等方法时,一定要持有与条件队列相关的锁
- 在检查条件谓词之后以及开始执行相关的操作之前,不要释放锁
丢失的信号
丢失的信号是指:线程必须等待一个已经为真的条件,但在开始等待之前没有检查条件谓词。
通知
每当在等待一个条件时,一定要确保在条件谓词变为真时通过某种方式发出通知
只有同时满足两个条件,才能用单一的notify而不是notifyAll
-
所有等待线程的类型都相同。只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作
-
单进单出。在条件变量上的每次通知,最多只能唤醒一个线程来执行。
普遍认可的做法是忧先使用 notifyAll 而不是notify. 虽然 notifyAll 可能比 notiy更低效,但却更容易确保类的行为是正确的。
在每个结程执行一个事件的同时,将出现大量的上下文切换操作以及发生竞争的锁获取操作。(最坏的情况是,在使notifyAll 时将导致 O(n^2)
次唤醒操作,而实际上只需要 n次唤醒操作就足够了)
public synchronized void put(V v) throws InterruptedException {
while (ifFull()) {
wait();
}
boolean wasEmpty = isEmpty();
doPut(v);
if (wasEmpty) {
notifyAll();
}
}
示例: 阀门类
使用wait和notifyAll来实现可重新关闭的阀门
@ThreadSafe
public class ThreaadGate {
@GuardedBy("this")
private boolean isOpen;
@GuardedBy("this")
private int generation;
public synchronized void close() {
isOpen = false;
}
public synchronized void open() {
++generation;
isOpen = true;
notifyAll();
}
public synchronized void await() throws InterruptedException {
int arrivalGeneration = generation;
while (!isOpen && arrivalGeneration == generation) {
wait();
}
}
}