概述
1.生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们 对多线程编程的理解更加深刻。
2.所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
3. 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
package com.gch.d11_wait_and_notify;
/**
作用:控制生产者和消费者的执行
*/
public class Desk {
// 是否有面条 0:没有面条 1:有面条
// boolean:true false(只有两个值,只能控制两条线程的执行)
public static int foodFlag = 0;
// 总个数 表示吃货最多可以吃十碗
public static int count = 10;
// 锁对象
public static final Object lock = new Object();
}
package com.gch.d11_wait_and_notify;
/**
生产者:表示厨师
*/
public class Producer extends Thread {
/**
* 调用父类的有参构造器
* @param name:线程名
*/
public Producer(String name){
super(name);
}
/*
1.循环
2.同步代码块
3.判断共享数据是否已经到了末尾(到了末尾)
4.判断共享数据是否已经到了末尾(没有到末尾,执行核心逻辑)
*/
@Override
public void run() {
// 1.循环
while(true){
// 2.同步代码块
synchronized(Desk.lock){
// 3.判断共享数据是否已经到了末尾(到了末尾)
if(Desk.count == 0){
break;
}else{
// 4.判断共享数据是否已经到了末尾(没有到末尾,执行核心逻辑)
// 判断桌子上是否有食物
if(Desk.foodFlag == 1){
try {
// 如果有,就等待
Desk.lock.wait(); // 让当前线程跟锁进行绑定起来
} catch (Exception e) {
e.printStackTrace();
}
}else{
// 如果没有,就制作食物
System.out.println("厨师做了一碗面条!");
// 修改桌子上的食物状态
Desk.foodFlag = 1;
// 叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
package com.gch.d11_wait_and_notify;
/**
消费者:表示吃货
*/
public class Consumer extends Thread {
/**
* 调用父类的有参构造器
* @param name:线程名
*/
public Consumer(String name){
super(name);
}
@Override
public void run() {
/*
1.循环
2.同步代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
// 1.循环
while(true){
// 2.同步代码块
synchronized(Desk.lock){
// 3.判断共享数据是否已经到了末尾(到了末尾)
if(Desk.count == 0){
break; // 循环一旦停止,线程就要结束了
}else{ // 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
// 先判断桌子上是否有面条
if(Desk.foodFlag == 0){
try {
// 如果没有,就等待
// 必须用锁对象去调用wait方法和notifyAll()方法
// Desk.lock.notifyAll(); // 唤醒跟这把锁绑定的所有线程
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (Exception e) {
e.printStackTrace();
}
}else{ // 如果有,就开吃
// 把吃的总数 - 1
Desk.count--;
// 如果有,就开吃
System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!");
// 吃完之后,唤醒厨师继续做
Desk.lock.notifyAll(); // 表示现在要去唤醒,绑定在这把锁上的所有线程
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
package com.gch.d11_wait_and_notify;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:完成生产者和消费者(等待唤醒机制)的代码
实现线程轮流交替执行的效果
*/
// 1.创建线程的对象
Thread p = new Producer("厨师");
Thread c = new Consumer("吃货");
// 2.开启线程
p.start();
c.start();
}
}
- 阻塞队列:连接生产者与消费者之间的管道。
- 阻塞队列实现了4个接口
- 阻塞队列由于实现了Collection接口,所以阻塞队列就是一个单列集合。
- 阻塞队列可以利用迭代器或者增强for循环来遍历。
- 生产者线程和消费者线程必须使用同一个阻塞队列!
- 有界表示是有长度的界限。因此在创建ArrayBlockingQueue对象的时候,必须要去只当队列的最大长度。
- 无界指的是没有长度的界限。
常见BlockingQueue(阻塞队列):
ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
package com.gch.d12_wait_and_notify;
import java.util.concurrent.ArrayBlockingQueue;
/**
生产者:表示厨师
*/
public class Producer extends Thread {
public ArrayBlockingQueue queue;
/**
* 有参构造器
* @param name:线程名
* @param queue:阻塞队列
*/
public Producer(String name, ArrayBlockingQueue queue) {
super(name);
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
// 1.不断的把面条放到阻塞队列当中
queue.put("面条"); // put方法底层源码就已经使用了Lock锁
System.out.println(getName() + "放了一碗面条!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package com.gch.d12_wait_and_notify;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 消费者:表示吃货
*/
public class Consumer extends Thread {
public ArrayBlockingQueue queue;
/**
* 有参构造器
* @param name:线程名
* @param queue:阻塞队列
*/
public Consumer(String name, ArrayBlockingQueue queue) {
super(name);
this.queue = queue;
}
@Override
public void run() {
while(true){
// 1.不断的从阻塞队列中获取面条
String food = null;
try {
food = queue.take(); // take方法的底层也是有Lock锁的
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package com.gch.d12_wait_and_notify;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:
生产者和消费者必须使用同一个阻塞队列
*/
// 1.创建阻塞队列的对象
// ArrayBlockingQueue是有界阻塞队列,在创建ArrayBlockingQueue对象的时候必须指定它的上限
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1);
// 2.创建线程的对象,并把阻塞队列传递过去
Thread p = new Producer("厨师",queue);
Thread c = new Consumer("吃货",queue);
// 3.开启线程
p.start();
c.start();
}
}