【大数据】学习笔记
1 Java SE
第9章 多线程
9.5 等待唤醒机制
9.5.1 线程间通信
为什么要处理线程间通信:
多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。而多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些通信机制,可以协调它们的工作,以此来帮我们达到多线程共同操作一份数据。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制。
9.5.2 等待唤醒机制
什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。
就是在一个线程满足某个条件时,就进入等待状态(wait()/wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
- wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”或者等待时间到,在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
- notify:则选取所通知对象的 wait set 中的一个线程释放;
- notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
被通知线程被唤醒后也不一定能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行) 状态;
- 否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态
调用wait和notify方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
9.5.3 生产者与消费者问题
等待唤醒机制可以解决经典的“生产者与消费者”的问题。
生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
生产者与消费者问题中其实隐含了两个问题:
- 线程安全问题:因为生产者与消费者共享数据缓冲区,不过这个问题可以使用同步解决。
- 线程的协调工作问题:
- 要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。
【1】一个厨师一个服务员问题
案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有1个厨师和1个服务员。
package com.dingjiaxiong.thread5;
/**
* @Projectname: BigDataStudy
* @Classname: TestCommunicate
* @Author: Ding Jiaxiong
* @Date:2023/4/27 16:12
*/
public class TestCommunicate {
public static void main(String[] args) {
// 1、创建资源类对象
Workbench workbench = new Workbench();
// 2、创建和启动厨师线程
new Thread("厨师") {
public void run() {
while (true) {
workbench.put();
}
}
}.start();
// 3、创建和启动服务员线程
new Thread("服务员") {
public void run() {
while (true) {
workbench.take();
}
}
}.start();
}
}
// 1、定义资源类
class Workbench {
private static final int MAX_VALUE = 10;
private int num;
public synchronized void put() {
if (num >= MAX_VALUE) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");
this.notify();
}
public synchronized void take() {
if (num <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");
this.notify();
}
}
【2】多个厨师多个服务员问题
案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有多个厨师和多个服务员。
package com.dingjiaxiong.thread5;
/**
* @Projectname: BigDataStudy
* @Classname: TestCommunicate2
* @Author: Ding Jiaxiong
* @Date:2023/4/27 16:13
*/
public class TestCommunicate2 {
public static void main(String[] args) {
// 1、创建资源类对象
WindowBoard windowBoard = new WindowBoard();
// 2、创建和启动厨师线程
// 3、创建和启动服务员线程
Cook c1 = new Cook("张三", windowBoard);
Cook c2 = new Cook("李四", windowBoard);
Waiter w1 = new Waiter("小红", windowBoard);
Waiter w2 = new Waiter("小绿", windowBoard);
c1.start();
c2.start();
w1.start();
w2.start();
}
}
//1、定义资源类
class WindowBoard {
private static final int MAX_VALUE = 10;
private int num;
public synchronized void put() {
while (num >= MAX_VALUE) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");
this.notifyAll();
}
public synchronized void take() {
while (num <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");
this.notifyAll();
}
}
//2、定义厨师类
class Cook extends Thread {
private WindowBoard windowBoard;
public Cook(String name, WindowBoard windowBoard) {
super(name);
this.windowBoard = windowBoard;
}
public void run() {
while (true) {
windowBoard.put();
}
}
}
//3、定义服务员类
class Waiter extends Thread {
private WindowBoard windowBoard;
public Waiter(String name, WindowBoard windowBoard) {
super(name);
this.windowBoard = windowBoard;
}
public void run() {
while (true) {
windowBoard.take();
}
}
}