如何创建线程安全的状态依赖的类?
状态依赖操作如一直阻塞直到某个条件为真。
四种实现方法:
方法一:在类库中现有状态依赖类的基础上进行构造
如利用BlockingQueue, FutureTask, CountdownLatch, Semaphore 等构建状态依赖的类。这是首选的简单安全的方法。
示例:下面的例子通过CountDownLatch只设值一次,如果没有设值就等待。
public class ValueLatch <T> {
private T value = null;
private final CountDownLatch done = new CountDownLatch(1);
public boolean isSet() {
return (done.getCount() == 0);
}
public synchronized void setValue(T newValue) {
if (!isSet()) {
value = newValue;
done.countDown();
}
}
public T getValue() throws InterruptedException {
done.await();
synchronized (this) {
return value;
}
}
}
方法二:内置的条件队列
条件队列:一种等待线程集合能够通过某种方式等待特定条件为真。
每个Java对象既可以作为锁,也可以作为条件队列。
Object的wait, notify, notifyAll 方法构造内部条件队列的API。
条件等待中的三元关系: 锁,wait方法和条件谓词。
条件谓词就是使某个操作称为状态依赖操作的前提条件。如缓存不为空时,take方法才能执行,否则等待。
状态依赖方法的典型模式:
void stateDependentMethod() throws InterruptedException {
//通过锁来保护条件谓词
synchronized(lock) {
while(!conditionPredicate())
lock.wait();
}
}
当使用条件等待时(Object.wait 或 Condition.await):
- 通常都有一个条件谓词对一些对象状态的测试。
- 在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试。
- 在一个循环中调用wait。
- 确保使用与条件谓词相关的锁来保护构成条件谓词的各个状态变量。
- 当调用wait, notify, notifyAll 方法时,一定要持有与条件队列相关的锁。
- 在检查条件谓词之后以及开始执行相应的操作之前,不要释放锁。
大多数情况下使用notifyAll,只有同时符合下面两个条件时才用notify:
1. 所有等待线程的类型都相同。只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作。
2. 单进单出。在条件变量的的每次通知,最多只唤醒一个线程来执行。
方法三:显示的Condition对象
内置条件队列有不足,如内置锁只能有一个相关联的条件队列。
Lock和Condition可以使用带有多个条件谓词的并发对象,或对条件队列有更多的控制权,如限时,公平性。
示例:
public class ConditionBoundedBuffer <T> {
protected final Lock lock = new ReentrantLock();
// CONDITION PREDICATE: notFull (count < items.length)
private final Condition notFull = lock.newCondition();
// CONDITION PREDICATE: notEmpty (count > 0)
private final Condition notEmpty = lock.newCondition();
private static final int BUFFER_SIZE = 100;
private final T[] items = (T[]) new Object[BUFFER_SIZE];
private int tail, head, count;
// BLOCKS-UNTIL: notFull
public void put(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[tail] = x;
if (++tail == items.length)
tail = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
// BLOCKS-UNTIL: notEmpty
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
T x = items[head];
items[head] = null;
if (++head == items.length)
head = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
方法四:AbstractQueuedSynchronizer(AQS)
AQS是一个用于构建锁和同步器的框架。
基于AQS的同步器,只可能在一个时刻发生阻塞,从而降低上下文切换的开销,提供吞吐量和可伸缩性。
在基于AQS的同步器,最基本的操作是获取操作和释放操作。
java.util.concurrent包中很多可阻塞类都是基于AQS,
如 ReentrantLock, Semaphore, CountdownLatch, ReentrantReadWriteLock, SynchronousQueue, FutureTask等。