一直搞不懂Java线程通信,这次终于明白了( 五 )

运行结果:发现收货员线程和送货员线程交替执行,并且库存满和送完之后都有对应的提示

一直搞不懂Java线程通信,这次终于明白了

文章插图
总结:在Condition中,用await()替换wait(),用signal()替换 notify(),用signalAll()替换notifyAll(),对于我们以前使用传统的Object方法,Condition都能够给予实现
Condition 精准唤醒不同的 Condition 可以用来等待和唤醒不同的线程,类似于上边我们说的等待池,但是Condition是通过队列实现等待和唤醒,Condition的await()方法,会使得当前线程进入等待队列并释放锁,同时线程状态变为等待状态 。当从await()返回时,当前线程一定是获取了Condition相关联的锁 。Condition实现方式在后边我们再分析
上边调用await 和 signalAll方法是控制所有该Condition对象的线程,我们有两个线程分别为收货和送货,我们可以创建两个Condition对象来精准控制等待和唤醒收货和送货线程 。
package com.stt.thread.communication;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;/**定义两个 Condition 对象,一个控制收货线程等待和唤醒,一个控制送货线程的等待和唤醒 */public class ExpressPoint {// 快递数量,使用原子类private AtomicInteger goodsNumber = new AtomicInteger();// 锁对象private ReentrantLock lock = new ReentrantLock();// 创建线程通信对象private Condition receivingCondition = lock.newCondition();private Condition dispatchCondition = lock.newCondition();// 收货方法,使用Lock锁,就不需要synchronized同步了public void receiving() {// 上锁lock.lock();// 写try...finally,保障无论是否发生异常都可以解锁,避免死锁try {// 判断是否继续接货while (goodsNumber.get() == 5) {System.out.println("库房已满,已不能再接收!");// 让收货线程进入等待receivingCondition.await();}System.out.println(Thread.currentThread().getName() + "已收到编号:" + goodsNumber.incrementAndGet() + "的包裹");// 仅仅唤醒送货线程dispatchCondition.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 解锁lock.unlock();}}// 派送方法public void dispatch() {// 上锁lock.lock();try {// 判断是否继续送货while (goodsNumber.get() == 0) {System.out.println("没有包裹,不能派送!");// 送货线程等待dispatchCondition.await();}System.out.println(Thread.currentThread().getName() + "已送出编号:" + goodsNumber.get() + "的包裹");goodsNumber.decrementAndGet();// 唤醒收货线程receivingCondition.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 解锁lock.unlock();}}}
一直搞不懂Java线程通信,这次终于明白了

文章插图
运行结果:运行结果是一样的,只是仅仅会让对应的线程等待和唤醒
一直搞不懂Java线程通信,这次终于明白了

文章插图
Condition实现分析等待队列Conditiont的等待队列是一个FIFO队列,队列的每个节点都是等待在Condition对象上的线程的引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await(),那么该线程就会释放锁,构成节点加入等待队列并进入等待状态 。
从下图可以看出来Condition拥有首尾节点的引用,而新增节点只需要将原有的尾节点nextWaiter指向它,并更新尾节点即可 。上述节点引用更新过程没有使用CAS机制,因为在调用await()的线程必定是获取了锁的线程,该过程由锁保证线程的安全 。
一个Lock(同步器)拥有一个同步队列和多个等待队列:
一直搞不懂Java线程通信,这次终于明白了

文章插图
如上边的例子:就是拥有receivingCondition 和 dispatchCondition两个等待队列
private Condition receivingCondition = lock.newCondition();private Condition dispatchCondition = lock.newCondition();等待调用Condition的await()方法,会使得当前线程进入等待队列并释放锁,同时线程状态变为等待状态 。当从await()返回时,当前线程一定是获取了Condition相关联的锁 。
线程触发await()这个过程可以看作是同步队列的首节点【当前线程肯定是成功获得了锁,才会执行await方法,因此一定是在同步队列的首节点】移动到了Condition的等待队列的尾节点,并释放同步状态进入等待状态,同时会唤醒同步队列的后继节点
一直搞不懂Java线程通信,这次终于明白了

文章插图
唤醒