文章目录
前言
Java多线程生产者和消费者(等待唤醒机制)的作用是实现生产者和消费者之间的协同工作,避免资源浪费和竞争。在多线程环境下,生产者负责生产数据,消费者负责消费数据。当生产者生产的数据过多时,需要等待消费者消费;当消费者消费的数据不足时,需要等待生产者生产。通过等待唤醒机制,可以实现生产者和消费者之间的同步,提高系统的效率。
一、生产者和消费者(等待唤醒机制)作用直观展示
如果有两条线程相互争夺运行,他的结果可能如下:
等待唤醒机制的作用就是打破这种随机性,让这两个线程轮流执行
生产者负责生产数据,消费者负责消费数据
二、生产者和消费者(常见方法)
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,知道被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
三、举例解释
我们通过厨师,食客和桌子的关系进行解释,规定桌子上只能放一盘食物,厨师做好食物放到桌子上由食客品尝。
在这个过程中,食客的行为有
同时,厨师的行为是
这就类似等待唤醒机制,让这个过程中的食客和厨师的关系变得有序。
四、代码示例
实现类代码
public class Demo {
public static void main(String[] args) {
//等待唤醒机制
//创建线程对象
Cook c = new Cook();
Foodie f = new Foodie();
//给线程设置名字
c.setName("厨师");
c.setName("吃货");
//开启线程
c.start();
f.start();
}
}
厨师类代码
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.foodFlog == 1){
//如果有,就等待
try {
Desk.Lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//如果没有,就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物状态
Desk.foodFlog = 1;
//等待消费者开吃
Desk.Lock.notifyAll();
}
}
}
}
}
}
食客类代码
public class Foodie extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.Lock){
if(Desk.count == 0){
break;
}else {
//先判断桌子上是否有面条
if(Desk.foodFlog == 0) {
//如果没有,就等待
try {
Desk.Lock.wait();//让当前线程和锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//把吃的总数-1
Desk.count--;
//如果有,就开吃
System.out.println("吃货在吃面条,还能再吃"+ Desk.count +"碗");
//吃完之后,唤醒厨师继续做
Desk.Lock.notifyAll();
//修改桌子的状态
Desk.foodFlog = 0;
}
}
}
}
}
}
桌子类代码
public class Desk {
//作用:控制生产者和消费者的执行
//0:没有食物 1:有食物
public static int foodFlog = 0;
//总个数
public static int count = 10;
//锁对象
public static Object Lock = new Object();
}
运行结果
厨师做了一碗面条
吃货在吃面条,还能再吃9碗
厨师做了一碗面条
吃货在吃面条,还能再吃8碗
厨师做了一碗面条
吃货在吃面条,还能再吃7碗
厨师做了一碗面条
吃货在吃面条,还能再吃6碗
厨师做了一碗面条
吃货在吃面条,还能再吃5碗
厨师做了一碗面条
吃货在吃面条,还能再吃4碗
厨师做了一碗面条
吃货在吃面条,还能再吃3碗
厨师做了一碗面条
吃货在吃面条,还能再吃2碗
厨师做了一碗面条
吃货在吃面条,还能再吃1碗
厨师做了一碗面条
吃货在吃面条,还能再吃0碗
五、等待唤醒机制第二种实现方式(阻塞队列方法实现)
1、简单理解
就相当于吃旋转小火锅,如果传送带上还有食物,食客就可以从上面取,同时,只要传送带上不是完全饱和的状态,厨师就可以往上面放食物。
2、阻塞队列的继承结构
3、代码示例
实现类
import java.util.concurrent.ArrayBlockingQueue;
public class Demo {
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();
}
}
厨师类
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){
//不断的把食物放入阻塞队列中
//put 和 take 源码中加了锁了
try {
queue.put("食物");
System.out.println("厨师放了食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
食客类
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 {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果示例
厨师放了食物
厨师放了食物
厨师放了食物
食物
食物
食物
厨师放了食物
厨师放了食物
食物
食物
厨师放了食物
厨师放了食物
食物
食物
厨师放了食物
厨师放了食物
食物
食物
厨师放了食物
解释:由于打印操作在take和put的锁的外面,所以打印的结果不规律,但是对数据的安全性是没有影响的。
总结
总结要点如下:
生产者消费者问题:是多线程编程中的一个经典同步问题,涉及到如何平衡生产和消费速率以避免资源浪费或竞争条件。
等待唤醒机制:通过使用wait()方法和notify()或notifyAll()方法,我们可以让线程在无法进行有效工作时进入等待状态,并在条件满足时被其他线程唤醒。
synchronized关键字:保证了同一时间只有一个线程能够访问共享资源,从而避免并发问题。
示例代码:我们构建了一个包含固定容量缓冲区的简单模型,其中生产者线程负责向缓冲区添加元素,而消费者线程则从缓冲区移除元素。当缓冲区满时,生产者线程会等待;当缓冲区为空时,消费者线程会等待。
实践意义:理解等待唤醒机制对于设计高效、可靠的并发程序至关重要,特别是在处理有限资源或需要协调多个独立活动的场景下。