等待唤醒机制

生产者和消费者模式

生产者消费者模式是一个十分经典的多线程协作的模式。

消费者等待

生产者等待

常见方法

方法名称

说明

void wait()

当前线程等待,直到被其他线程唤醒

void notify()

随机唤醒单个线程

void notifyAll()

唤醒所有线程

厨子生产数据(生产者):

// 厨子
public class Cook 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.foodFlag = 1;
 // 叫醒等待的消费者开吃
 Desk.lock.notifyAll();
 }
 }
 }
 }
 }
}

吃货消费数据(消费者):

// 吃货
public class Foodie extends Thread{
 @Override
 public void run() {
 /*
 * 1.循环
 * 2.同步代码块
 * 3.判断共享数据是否到了末尾(到了末尾)
 * 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
 */
 while (true){
 synchronized (Desk.lock){
 if(Desk.count == 0){
 break;
 }else {
 // 先判断桌子上是否有面条
 if (Desk.foodFlag == 0){
 // 如果没有,就等待
 try {
 Desk.lock.wait();
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 }else {
 // 把吃的总数-1
 Desk.count--;
 // 如果有,就开吃
 System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!");
 // 吃完之后,唤醒厨师(生产者)
 Desk.lock.notifyAll();
 // 修改桌子的状态
 Desk.foodFlag = 0;
 }
 }
 }
 }
 }
}

桌子(数据协调):

public class Desk {
 /*
 作用:控制生产者和消费者的执行
 */

 // 是否有面条 0:没有面条 1:有面条
 public static int foodFlag = 0;

 // 总个数,表示消费者执行的总次数
 public static int count = 10;

 // 锁对象
 public static Object lock = new Object();
}


阻塞队列方式实现

阻塞队列体现结构

Java多线程​(四)等待唤醒机制及线程状态_System


另外两个实现类是

  • ArrayBlockingQueue底层是数组,有界
  • LinkedBlockingQueue 底层是链表,无界。但不是真正的无界,最大为int的最大值。

我们可以通过使用ArrayBlockingQueue和LinkedBlockingQueue对象来实现阻塞队列的线程调用方式。在多个线程之间需要共享一个阻塞队列,Java帮我们实现了同步的问题。

生产者:

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{
 // 阻塞队列变量,共享
 ArrayBlockingQueue<String> queue;

 public Cook(ArrayBlockingQueue<String> queue){
 this.queue = queue;
 }

 @Override
 public void run() {
 while (true){
 // 不断的做出面条,放到阻塞队列中
 // 注意这里不需要使用synchronized语句块或锁,
 // 因为ArrayBlockingQueue内部确保了同步
 try {
 queue.put("面条");
 System.out.println("厨师做了一碗面条!");
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 }
 }
}

消费者:

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){
 // 不断的从阻塞队列中获取面条
 // 底层也是加锁的,因此不需要再加synchronized
 try {
 String food = queue.take();
 System.out.println(food);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 }
 }
}

使用方法:

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
 public static void main(String[] args) {
 /*
 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:
生产者和消费者必须使用同一个阻塞队列
 */
 // 1.创建阻塞队列对象
 ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

 // 2.创建线程对象,并把阻塞队列传递进去
 Cook c = new Cook(queue);
 Foodie f = new Foodie(queue);

 // 3.开启线程
 c.start();
 f.start();
 }
}


线程的状态

在前面章节中已经谈到线程的生命周期,这里需要再补充线程的状态。可以使用ThreadState类定义线程状态。

线程的七大状态:

Java多线程​(四)等待唤醒机制及线程状态_ide_02


注意JVM中没有定义运行状态,因为线程一旦运行起来即交由操作系统处理。因此在Java中,有以下几个状态:

Java多线程​(四)等待唤醒机制及线程状态_阻塞队列_03