之前在介绍线程的状态及转换中,提到了Object有wait()、wait(long timeout)、notify()、notifyAll()方法,这些方法与synchronized配合使用,可以实现等待/通知模式。无独有偶,Conditon接口也提供了类似Object的监视器方法,与Lock配合也能实现通知/等待模式。
1、Condition接口
①接口介绍
在上篇显式锁(一)介绍Lock接口时,Lock有一个方法叫" Condition newCondition(); ",此方法是获取等待通知组件,也就是Conditon,获取的Condition与此Lock绑定。也可以说,Conditon是依赖Lock对象。
java-8
public interface Condition {
//当前线程进入等待状态直到被通知或中断
void await() throws InterruptedException;
//当前线程进入等待状态直到被通知,对中断不敏感
void awaitUninterruptibly();
//当前线程进入等待状态直到被通知、中断或超时
long awaitNanos(long nanosTimeout) throws InterruptedException;
//当前线程进入等待状态直到被通知、中断,在指定时间内被通知返回true,否则false
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒一个等待在Condition上的线程
void signal();
//唤醒所有等待在Conditon上的线程
void signalAll();
}
一般的使用套路如下:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
lock.lock();
try{
condition.await();
}finally{
lock.unlock();
}
}
public void conditionSingal() throws InterruptedException{
lock.lock();
try{
condition.singal();
}finally{
lock.unlock();
}
}
②使用示例
通过一个生产者/消费者的例子来使用下Condition:
/**
* Created by bxw on 2017/10/5.
*/
public class Depot {
private int capacity;
private int size;
private Lock lock;
private Condition consumerCond;
private Condition produceCond;
public Depot(int capacity) {
this.capacity = capacity;
this.size = 0;
this.lock = new ReentrantLock();
this.consumerCond = lock.newCondition();
this.produceCond = lock.newCondition();
}
public void produce(int val){
lock.lock();
try {
int left = val;
while(left > 0){
while(size >= capacity){
produceCond.await();
}
int produce = (left + size) > capacity ? (capacity - size) : left;
size += produce;
left -= produce;
System.out.println(Thread.currentThread().getName() + ", ProcudeVal=" + val +", produce=" + produce + ",size=" + size);
consumerCond.signalAll();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void consumer(int val){
lock.lock();
try {
int left = val;
while (left > 0) {
while (size <= 0) {
consumerCond.await();
}
int consumer = (size <= left) ? size : left;
size -= consumer;
left -= consumer;
System.out.println(Thread.currentThread().getName() + ", ConsumerVal=" + val + ", consumer=" + consumer + ", size=" + size);
produceCond.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class Producer {
private Depot depot;
public Producer(Depot depot) {
this.depot = depot;
}
public void produceThing(final int amount) {
new Thread(new Runnable() {
public void run() {
depot.produce(amount);
}
}).start();
}
}
public class Consumer {
private Depot depot;
public Consumer(Depot depot) {
this.depot = depot;
}
public void consumerThing(final int amount) {
new Thread(new Runnable() {
public void run() {
depot.consumer(amount);
}
}).start();
}
}
public class Test {
public static void main(String[] args) {
// 仓库
Depot depot = new Depot(100);
// 消费者
Consumer consumer = new Consumer(depot);
// 生产者
Producer produce = new Producer(depot);
produce.produceThing(5);
consumer.consumerThing(5);
produce.produceThing(2);
consumer.consumerThing(5);
produce.produceThing(5);
}
}
运行结果:
从结果看出,Thread-0和Thread-1比较和谐,Thread-0生产了5个,Thread-1消费了5个,剩余0。接着Thread-2又生产了2个,Thread-3需要消费5个,但仓库中只有2个,所以先将库存的2个产品全部消费,然后这个线程进入等待队列,等待生产,随后生产出了5个产品,生产者生产后又执行signalAll方法将等待队列中所有的线程都唤醒,Thread-3继续消费还需要的3个产品,仓库还剩余了2个。
③实现分析
首先从Lock.newCondition分析,以ReentrantLock为例,会返回一个ConditionObject对象。
那ConditionObject为何物,原来它是AbstractQueuedSynchronizer(简称AQS)的一个内部类,拥有两个节点:头节点、尾节点。
因为每个ConditionObject都包含一个等待队列,这是Conditon实现等待/通知的关键。等待队列是个FIFO队列,队列中每个节点都包含了一个线程的引用,该线程是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,该线程就释放锁、构造节点加入等待队列并进入等待状态。节点(Node)的定义也是AQS中的一个内部类。
Condition拥有首节点的引用,新增节点只需要把原来的尾节点nextWaiter指向它,并且更新尾节点即可。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列关系图如下:
从队列角度看,调用Condition的await()方法,就是把同步队列的首节点移动到了Condition的等待队列,线程状态变为等待状态。
总结
此篇主要介绍了依赖Lock的Condition,Condition的等待/通知模型和Object的等待/通知模型有些相似,两者可以对比着使用,分析了Condition的简单实现原理,引出了队列同步器的概念,下篇就简单介绍下AQS。
参考书籍《并发编程的艺术》,
案例参考博客 http://www.cnblogs.com/zhengbin/p/6420984.html
略陈固陋,如有不当之处,欢迎各位看官批评指正!