等待唤醒机制(部分转载)
- 等待唤醒机制基本实现
- 等待唤醒机制阻塞队列实现0
等待唤醒机制的基本实现
一.循环等待问题
假设今天要发工资,强老板要去吃一顿好的,整个就餐流程可以分为以下几个步骤:
1.点餐
2.窗口等待出餐
3.就餐
public static void main(String[] args) {
// 是否还有包子
AtomicBoolean hasBun = new AtomicBoolean();
// 包子铺老板
new Thread(() -> {
try {
// 一直循环查看是否还有包子
while (true) {
if (hasBun.get()) {
System.out.println("老板:检查一下是否还剩下包子...");
Thread.sleep(3000);
} else {
System.out.println("老板:没有包子了, 马上开始制作...");
Thread.sleep(1000);
System.out.println("老板:包子出锅咯....");
hasBun.set(true);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println("小强:我要买包子...");
try {
// 每隔一段时间询问是否完成
while (!hasBun.get()) {
System.out.println("小强:包子咋还没做好呢~");
Thread.sleep(3000);
}
System.out.println("小强:终于吃上包子了....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
在这种情况下我们的老板需要不停的检查是否还有包子,而我们的小强需要不停的检查包子是否制作完成
,则暴露了一个问题:不断通过轮询机制来检测条件是否成立, 如果轮询时间过小则会浪费CPU资源,如果间隔过大,又导致不能及时获取想要的资源。
等待唤醒机制基本实现
为了解决循环等待消耗CPU以及信息及时性问题,Java中提供了等待唤醒机制。通俗来讲就是由主动变为被动, 当条件成立时,主动通知对应的线程,而不是让线程本身来询问。
我们之前的线程都是随机抢占CPU,而等待唤醒机制可以打破线程随机执行,使得线程的执行变得井然有序
- 消费者
package com.cook.foodie;
public class Foodie extends Thread{
@Override
public void run(){
/*
1.循环
2.通过代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据时候到了默认(没有到默认执行核心逻辑)
*/
// 1.循环
while (true){
//2.通过代码块
synchronized (Desk.lock){
//3.判断共享数据是否到了末尾(到了)
if(Desk.count == 0){
break;
}else {
//4.执行核心逻辑
//先判断桌子上是否有面条
if(Desk.foodFlag == 0){
//如果没有面条继续等待
try {
Desk.lock.wait();//此时的wait方法务必用锁对象调用
//表示当前线程和锁进行绑定(唤醒的时候就只唤醒和这把锁绑定的线程)
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//如果有面条就吃面条
Desk.count--;
System.out.println("吃货正在吃面条,还能吃"+Desk.count+"碗面条");
//吃完后唤醒厨师继续做面条
Desk.lock.notifyAll();//唤醒和这把锁绑定的线程
//修改桌子的状态
Desk.foodFlag = 0;//将桌子的状态改为没有面条
}
}
}
}
}
}
- 生产者
package com.cook.foodie;
public class Cooker extends Thread{
@Override
public void run(){
/*
1.循环
2.通过代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据时候到了默认(没有到默认执行核心逻辑)
*/
while (true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
//判断桌子上是否有食物
if(Desk.foodFlag == 1){
//如果有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//如果没有就制作食物
System.out.println("制作了一碗食物");
//通知(唤醒)消费者吃食物
Desk.lock.notifyAll();
Desk.foodFlag = 1;
}
//修改桌子上的食物状态
}
}
}
}
}
- 控制者(也可以不用)
package com.cook.foodie;
public class Desk {
//作用:控制生产者和消费者的执行
//是否有面条 0 表示没有面条 1 表示有面条
public static int foodFlag = 0;
//总数量
public static int count = 10;//一共只能吃10碗面
//锁对象
public static Object lock = new Object();
}
- 测试类
package com.cook.foodie;
//测试类(唤醒机制的演示)
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
Cooker c = new Cooker();
Foodie f = new Foodie();
//给线程设置名字
c.setName("厨师");
f.setName("吃货");
//开启线程
c.start();
f.start();
}
}
等待唤醒机制的阻塞队列实现
为了简便实现等待唤醒机制,Java提供了阻塞队列。通过阻塞队列的实现不同队列长度可以是固定的也可以是无限的。当放数据放不下将会阻塞,当取数据取不到也会阻塞
- 生产者
package com.cook.foodie1;
import java.util.concurrent.ArrayBlockingQueue;
//要求对于厨师和吃货使用的都是同一个阻塞队列
public class Cooker extends Thread{
ArrayBlockingQueue<String> queue ;
public Cooker(ArrayBlockingQueue queue){
this.queue= queue;
}
@Override
public void run(){
while (true){
try {
//不断的向队列里面放面条
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 消费者
package com.cook.foodie1;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;//定义阻塞队列
public Foodie(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run (){
while (true){
try {
//不断从阻塞队列中获取食物
final String food = queue.take();//take方法的底层也有锁
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 测试类
package com.cook.foodie1;
import java.util.concurrent.ArrayBlockingQueue;
//用阻塞队列实现多线程的等待唤醒机制
public class FoodieTest {
public static void main(String[] args) {
//细节:生产者和消费者必须使用同一个阻塞队列
//1.创建阻塞队列并指定容量
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建线程对象并转递阻塞队列
Cooker c = new Cooker(queue);
Foodie f = new Foodie(queue);
//3.开启线程
c.start();
f.start();
}
}
//没有确定可以吃多少碗,所以程序是个死循环
在我们的put和take方法中已经写了同步代码块