前面章节我们学习了wait/notify
的相关知识,也知道他们的组合拳可以让线程避免自旋,从而降低性能损耗,减少对CPU的浪费。今天要介绍的是它的威力加强版,也就是Condition
的使用,整体知识点不是很多,相信小伙伴们可以轻松掌握。
一、Condition简介
在Java1.5之前,开发者主要使用await/notify来实现线程之间的协作,而在1.5版本中,Condition正式诞生,它的出现其实就是为了替代前者。上述两者的使用场景相同,都是用在一些典型的模型中,如等待-通知模型、生产-消费模型。
- 使用场景
当线程1执行过程中,需要【等待某个条件】才能继续执行,此时线程1会主动调用await()方法进入阻塞状态;然后线程2去完成这个条件,当条件完成后,线程2调用signal()
方法,这时JVM会就会从阻塞线程中查找等待condition的线程1,线程1收到信号后,它的线程状态就会变成可执行状态。
- 优势
wait/notify
是Java底层级方法,通过与对象监视器完成线程合作,而Condition
是JUC中的工具类,它是通过与Lock
协作完成流程控制,具有很高的扩张性,同时增加很多特性,如支持不响应中断、支持多种等待条件。
二、Condition核心方法
Condition
是一个接口,我们先看下其内部有哪些方法:
- await: 当前线程进入等待,直到收到信号(
signal
)或者被中断; - await(long time, TimeUnit unit): 当前线程进入等待,直到收到信号或者被中断、到达指定等待时间(
time
); - awaitNanos(long nanosTimeout): 和上述方法一样,主要区别为时间单位不同,且当前方法返回值表示剩余超时时间。
- awaitUninterruptibly(): 当前线程进入等待,直到收到信号,该方法不响应中断;
- awaitUntil(Date deadline): 当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回true,否则,表示到了指定时间,返回false
- signal(): 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁。
- signalAll(): 唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁。
三、Condition代码演示
- 基本用法
首先我们看一下Condition
的基本用法,线程调用await
方法开始阻塞,调用signal
方法唤醒等待中的线程。
public class ConditionDemo {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void method_1() throws InterruptedException {
lock.lock();
try{
System.out.println("条件不满足,开始阻塞");
condition.await();
System.out.println("条件满足,继续执行");
}finally {
lock.unlock();
}
}
void method_2() {
lock.lock();
try{
System.out.println("执行完所需条件,开始唤醒等待的线程");
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionDemo conditionDemo = new ConditionDemo();
//线程2负责唤醒阻塞的线程
new Thread(() -> {
try {
Thread.sleep(1000);
conditionDemo.method_2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//线程1执行方法1
conditionDemo.method_1();
}
}
打印结果如下:
可以看到,线程1执行到await()方法后会进行阻塞,直到线程2调用signal()方法,线程1才恢复执行。
有一点需要注意,等待线程
和唤醒线程
不能为同一个线程,例如将上面案例修改为下面这段,那程序会进入无休止的等待中。原因也非常简单,因为当线程调用完method_1
时就已经陷入阻塞,不会再继续执行method_2
了。
//修改代码如下,程序将陷入阻塞
conditionDemo.method_1();
conditionDemo.method_2();
- Condition实现生产者消费者模式
前面我们用wait/notify实现过生产者消费者模式,今天我们看一下使用Condition
是如何实现的,下面代码。
public class ConditionDemo2 {
private int queueMaxSize = 5;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueMaxSize);
private Lock lock = new ReentrantLock();
private Condition consConditon = lock.newCondition();
private Condition prodConditon = lock.newCondition();
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
//消费者线程
class Consumer extends Thread {
@Override
public void run() {
consume();
}
private void consume() {
while (true) {
lock.lock();
try {
while (queue.size() == 0) {
System.out.println("队列空,等待数据");
try {
prodConditon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll();
consConditon.signalAll();
System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
} finally {
lock.unlock();
}
}
}
}
//生产者线程
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
while (true) {
lock.lock();
try {
while (queue.size() == queueMaxSize) {
System.out.println("队列已满,等待消费者消费");
try {
consConditon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
prodConditon.signalAll();
System.out.println("插入元素,队列当前数量" + queue.size());
} finally {
lock.unlock();
}
}
}
}
}
结果如下:
分析一下上述代码的执行流程:
- 生产者线程获取锁,当队列数量小于队列最大数量时,开始插入数据;
- 插入完数据,生产者调用
signalAll
唤醒阻塞中的消费者线程; - 消费者线程尝试获取锁,如果成功获取,会消费队列中的数据,如果没有获取到,那生产者线程会继续插入数据到队列中,直至队列数量达到最大时,生产者会调用
await
进入阻塞; - 消费者消费完毕后,消费者会调用
signalAll
方法唤醒阻塞中的生产者线程,当生产者没有获取到锁,那消费者会持续消费,直到队列为空时,消费者会调用await
进入阻塞。
四、Condition注意事项
Condition
用于替代wait/notify
来实现流程控制,在用法和性质上,两者基本一致。await
方法子自动释放持有的Lock锁,这点和Object.wait()
一样,无需主动释放。- 调用
await
的时候,必须持有锁,否则会抛出异常,也是和Object.wait()
一样。
总结
Condition
的用法比较简单,结合之前介绍的wait/notify,相信小伙伴们也是很轻松就能掌握。最近在整理一些其他专栏,所以更新也比较慢,下一章预计更新最后一种流程控制类CyclicBarrier
。
点关注,防走丢
以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花Gie ,有问题大家随时留言讨论 ,我们下期见🦮。
原创不易,你们的点赞是我坚持的动力,如果你觉得这篇文章对你有点用的话,感谢老铁为本文点个赞、评论或转发一下
,因为这将是我输出更多优质文章的动力,感谢!