程序猿学社的GitHub,欢迎Star
https://github.com/ITfqyd/cxyxs
本文已记录到github,形成对应专题。
前言
生产者和消费者问题,是面试过程中,经常问到的一个问题,本文,就来了解一下,生产者和消费者是怎么一回事。
1.概念
生产者消费者问题是一个著名的线程同步问题,也是我们面试过程中,经常会问到的一个经典面试题,生产者负责生产数据放到缓冲区,消费者负责从缓存中里面取。
- 注意这个缓冲区的大小是有上限的。
- 生产者发现供过于求,也就是说生产的速度太快了,也就是说,缓冲区里的数据,远远高于消费者的消费的能力,如果我们不做处理,就会造成速度的堆积,所以,该缓冲区是有上限的。可以理解为一个仓库,只能存放一定数据的产品,仓库都堆放满了,已经无地可放产品了,只能等消费者购买产品后,我们才能继续生产产品。
- 生产者发现供不应求,说明消费者消费的速度远远高于生产者的速度,这时候,我们就只能耐心的等待。应该避免库里已经没有产品了还在消费。
提到生产者和消费者,顿时,我们的脑海里面会浮现不少的产品,rabbitmq,kafka,dubbo等等。
2.模拟真实业务场景
深圳某充电桩厂家,有一个小库房,能存放10台充电桩,为了保证不必要的浪费,利益最大化,要求库房满了,就停止生产,也不能造成库存的数据远远小于消费的数量。
代码实战
通过synchronized+wait+notifyall实现
package com.cxyxs.thread.eleven;
/**
* Description:
* 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/
* Author: 程序猿学社
* Date: 2020/2/29 10:25
* Modified By:
*/
public class Pile {
//库存数量
private int sum=0;
private int max=10;
/**
* 生产者
*/
public synchronized void pro(){
while (sum == max){ //达到最大值说明库存已经满了,无法存放充电桩
try {
this.wait(); //需要等待消费者,消费后,才能生存
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sum++;
System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
this.notifyAll(); //通知消费者,我们库存有充电桩,赶紧来消费
}
/**
* 消费者
*/
public synchronized void consumer(){
while (sum == 0){
try {
this.wait(); //说明库房,充电桩不够了,需要等待充电桩厂家,继续生产充电桩
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sum --;
System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
this.notifyAll(); //通知生产厂家,我已经消费了,你可以继续生产充电桩
}
}
测试类
package com.cxyxs.thread.eleven;
/**
-
Description:synchronized+wait+notifyall解决生产者和消费者问题
-
转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/
-
Author: 程序猿学社
-
Date: 2020/2/29 10:45
-
Modified By:
*/
public class Demo1{
public static void main(String[] args) {
Pile pile = new Pile();for (int j = 0; j < 3; j++) { new Thread(()->{ for (int i = 0; i < 15; i++) { pile.pro(); } },"生产者"+j).start(); new Thread(()->{ for (int i = 0; i < 15; i++) { pile.consumer(); } },"消费者"+j).start(); }
}
}
通过测试结果,我们可以看出,不会存在生产超出库存最大的情况,也不会出现没有产品后还在消费的问题。
- 重点,注意判断是否进入等待的时候,使用while,而不是if。如果使用if,可能会存在虚假唤醒的问题,所以这里,使用while,再次判断,避免虚假唤醒的问题。
lock+await+signalAll
java1.5版本以后,引入新的三剑客,一代新人换旧人。我们来看一看1.5版本的三剑客都有哪些。
- lock ----------synchronized
- wait ---------- await
- notifyall ----- signal
synchronized可以理解为自动挡,会自动释放锁
Lock可以理解为手动挡,这种方式虽说灵活,但是需要自己手动释放锁,为了防止死锁,我们lock一般与try finally配套使用。
package com.cxyxs.thread.eleven;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Description:Lock方式
* 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/
* Author: 程序猿学社
* Date: 2020/2/29 18:16
* Modified By:
*/
public class PileLock {
//库存数量
private int sum=0;
private int max=10;
final Lock lock = new ReentrantLock();
private Condition pro= lock.newCondition();
private Condition consumer= lock.newCondition();
/**
* 生产者
*/
public void pro(){
try {
lock.lock(); //上锁
while (sum == max){ //达到最大值说明库存已经满了,无法存放充电桩
try {
pro.await(); //需要等待消费者,消费后,才能生存
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sum++;
System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
pro.signalAll(); //通知消费者,我们库存有充电桩,赶紧来消费
}finally {
lock.unlock(); //重点
}
}
/**
* 消费者
*/
public void consumer(){
try {
lock.lock(); //上锁
while (sum == 0){
try {
consumer.await(); //说明库房,充电桩不够了,需要等待充电桩厂家,继续生产充电桩
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sum --;
System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
consumer.signalAll(); //通知生产厂家,我已经消费了,你可以继续生产充电桩
} finally {
lock.unlock(); //重点
}
}
}
测试类
package com.cxyxs.thread.eleven;
/**
* Description:synchronized+wait+notifyall解决生产者和消费者问题
* 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/
* Author: 程序猿学社
* Date: 2020/2/29 10:45
* Modified By:
*/
public class Demo1{
public static void main(String[] args) {
// 通过synchronized+wait+notifyall实现
// Pile pile = new Pile();
//lock+await+signal
PileLock pile = new PileLock();
for (int j = 0; j < 3; j++) {
new Thread(()->{
for (int i = 0; i < 10; i++) {
pile.pro();
}
},"生产者"+j).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
pile.consumer();
}
},"消费者"+j).start();
}
}
}
- 注意一定要用try finally,通过finally设置解锁,以确保在必要时释放锁定。
- 我们使用Lock来产生两个Condition对象来管理任务间的通信,一个是生产者,一个是消费者。
- lock() 获取锁,unlock() 释放锁
- await() 使当前线程加入 等待队列中,并释放当锁.当其他线程调用signal()会重新请求锁
- signal会唤醒一个在 await()等待队列中的线程,signalAll会唤醒 await()等待队列中所有的线程