从有点基础开始学JUC:线程间定制化通信
线程间定制化通信
概念
多个线程之间的执行时机并不是固定的,是由 CPU 来操作调度的,而如果要让线程按照我们需要的顺序执行,那这就是线程间定制化通信
实现
因为一般的notify()
和notifyAll()
方法唤醒的线程具有强烈的随机性,所以我们这里采用Condition接口
。
Condition接口
的特殊之处在于:
相较于synchronized
只能与一个对象的监视器相关联,而ReentrantLock对象
,则可以通过反复调用newCondition()
方法,创建多个Condition对象
,而这些Condition对象
则与lock
的监视器相对应,可以自由的唤醒对应的Condition对象
的线程
Condition
概述
在开始写正式的代码之前,我们先来补充一些Condition的基础知识
每个Condition对象都包含一个队列(等待队列)。等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态
常用方法
await() | 造成当前线程在接到信号或被中断之前一直处于等待状态。 |
boolean await(long time, TimeUnit unit) | 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态->是否超时,超时异常 |
awaitNanos(long nanosTimeout) | 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。 |
awaitUninterruptibly() | 造成当前线程在接到信号之前一直处于等待状态。(注意:该方法对中断不敏感)。 |
awaitUntil(Date deadline) | 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。 |
signal() | 唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。 |
signalAll() | 唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。 |
原理简述
每个Condition对象都包含一个队列(等待队列)。等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。等待队列的基本结构如下所示:
注意:
- 等待队列分为首节点和尾节点。当一个线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。
- 新增节点就是将尾部节点指向新增的节点。节点引用更新本来就是在获取锁以后的操作,所以不需要CAS保证。同时也是线程安全的操作
- 调用Condition的signal()方法,将会唤醒在等待队列中等待最长时间的节点(条件队列里的首节点)
代码总览
现在,我们来看一个代码
public class Restaurant {
private final Lock lock = new ReentrantLock();
private final Condition customerCondition = lock.newCondition();
private final Condition waiterCondition = lock.newCondition();
private final Condition chefCondition = lock.newCondition();
private boolean isOrderReceived = false;
private boolean isMealServed = false;
public void reception() {
lock.lock();
try {
System.out.println("服务生接待顾客");
customerCondition.signal();
while (!isOrderReceived) {
waiterCondition.await();
}
System.out.println("顾客点餐完毕,服务生给厨师菜单");
chefCondition.signal();
while (!isMealServed) {
waiterCondition.await();
}
System.out.println("将饭菜给顾客");
customerCondition.signal();
while (isMealServed) {
waiterCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void orderFood(){
lock.lock();
try {
System.out.println("用户点餐");
isOrderReceived = true;
waiterCondition.signal();
while (!isMealServed) {
customerCondition.await();
}
System.out.println("顾客用餐");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void cook() {
lock.lock();
try {
System.out.println("厨师做饭");
isOrderReceived = false;
isMealServed = true;
waiterCondition.signal();
while (isOrderReceived) {
chefCondition.await();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
这里我们使用使用newCondition()
方法新建了三个Condition对象
,同时我们定义了两个标志变量:isOrderReceived
和isMealServed
,分别表示顾客是否已经点餐和饭菜是否已经送到顾客手中。然后是三个方法,分别是reception()
(接待)、orderFood()
(点餐)、cook()
(做饭),用于模拟服务生接待顾客、顾客点餐吃饭、厨师做饭。
接下来,我们看看这三个代码的具体执行流程
具体执行流程
-
首先,我们要执行
reception()
方法 -
然后服务生要先接待顾客,然后则到了点餐的时候,所以这时候要先唤醒
customerCondition对象
的线程(也就是顾客执行操作的线程),然后判断顾客是否已经点餐了,如果还没有点餐,则使自身线程休眠System.out.println("服务生接待顾客"); customerCondition.signal(); while (!isOrderReceived) { waiterCondition.await(); }
-
然后则是用户点餐,点餐完成之后就到了服务生将菜单给厨师,让厨师做菜,所以首先要让
isOrderReceived
标志变量为true,然后重新唤醒waiterCondition
对应的线程,最后,在服务生未将饭菜给顾客之前,要保持线程的休眠System.out.println("用户点餐"); isOrderReceived = true; waiterCondition.signal(); while (!isMealServed) { customerCondition.await(); }
-
接着是服务生将菜单给厨师,所以要唤醒
chefCondition
对应的线程,同时保持自身线程的休眠System.out.println("顾客点餐完毕,服务生给厨师菜单"); chefCondition.signal(); while (!isMealServed) { waiterCondition.await(); }
-
然后是厨师根据菜单做菜,首先要将
isOrderReceived
设置为false,表示当前菜单已经处理了,和isMealServed
设置为true,表示厨师已经做出饭菜,接着唤醒waiterCondition
对应的线程,让服务生将饭菜给顾客,同时在下一份饭菜来之前,保持自身线程的休眠System.out.println("厨师做饭"); isOrderReceived = false; isMealServed = true; waiterCondition.signal(); while (isOrderReceived) { chefCondition.await(); }
-
服务生受到厨师做完菜的消息后,将食物给顾客,同时唤醒customerCondition对应的线程,让顾客消费,同时保持自身线程的休眠
System.out.println("将饭菜给顾客"); customerCondition.signal(); while (isMealServed) { waiterCondition.await(); }
-
最后,顾客用餐
System.out.println("顾客用餐");
执行代码
然后,来看我们的运行的代码:
public class RestaurantTest {
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
new Thread(restaurant::reception, "reception").start();
new Thread(restaurant::orderFood, "orderFood").start();
new Thread(restaurant::cook, "cook").start();
}
}
新建三个线程执行代码,下面是执行的结果:
服务生接待顾客
用户点餐
顾客点餐完毕,服务生给厨师菜单
厨师做饭
将饭菜给顾客
顾客用餐
这就是这篇文章的内容了,欢迎大家的讨论,如有错漏,也请指出,谢谢~