参考文章
【操作系统】生产者消费者问题_liushall-CSDN博客_生产者消费者问题操作系统
概念
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。
可以理解为肯德基买炸鸡,
生产者就相当于肯德基的后厨,生产炸鸡并放到前台
消费者就相当于顾客,从前台获得炸鸡
有限的缓冲区就相当于前台的保温柜里能放几只炸鸡
但是生产炸鸡的时候顾客不能购买,购买炸鸡的时候后厨不能生产
注意事项:
当缓冲区(前台)为空时,消费者(顾客)不能再进行消费
当缓冲区(前台)为满时,生产者(后厨)不能再进行生产
在线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程的同步
通过传统synchronized锁来实现生产者消费者问题
实现生产者消费者问题的代码逻辑为
也既,先判断满不满足条件,不满足让出CPU,直到再次被唤醒,满足则执行业务操作,执行完成后唤醒其他线程
public class Demo02pc {
public static void main(String[] args) {
Production production = new Production();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.increase();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.decrease();
}
},"B").start();
}
}
class Production{
private int product=0;
public synchronized void increase(){
if (product!=0){ //不满足条件
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product++; //完成生产的业务操作
System.out.println("生产者为:"+Thread.currentThread().getName());
//唤醒其他进程
this.notifyAll();
}
public synchronized void decrease(){
if(product==0){ //不满足条件
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务
product--; //完成生产的业务操作
System.out.println("消费者为:"+Thread.currentThread().getName());
//唤醒
this.notifyAll();
}
}
运行后可以成功的模拟
但是这只是一个生产者和一个消费者的问题,如果有多个生产者和消费者,用if就会出现问题
因为if只会判断一次
具体这个地方的理解方法,暂时还没有找到很好的解释,自己理解下来就是
假如有两个生产者AC,两个消费者BD,当此时product为1,而且A和C同时进入增加状态,首先会判断product是否为1,这里product为1,所以线程AC都会进入等待状态,这时如果进入了一个消费者线程,将product减为0,并且唤醒所有等待的线程,因为是if判断,只会判断一次,所以两个生产者线程都会执行业务代码让product++,所以就会出现product为2的情况,这就叫做虚假唤醒。
画个图可以更形象的说明
一开始AC因为有产品,所以都进入了休眠状态等待没有产品的时候出来干活
这时候来了个线程D,把唯一的一个产品消耗了,然后唤醒了所有的线程A和C,这是A和C都通过了if判断,只不过暂时的“睡着了”,所以他们醒来以后就直接进行了业务操作,两次product++,那可不就是生成了两个产品嘛
那要怎么解决这个问题呢,其实很简单,在两个进程被唤醒后,再加一道循环不就行了嘛,所以我们这里就可以通过while循环来对进程进行判断,这样就算有两个生产者被唤醒,也只能有一个生产者能进入业务功能,另一个生产者则会通过while循环再次“睡去”
修改后的代码
package com.kuang;
public class Demo02pc {
public static void main(String[] args) {
Production production = new Production();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.increase();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.decrease();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.increase();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.decrease();
}
},"D").start();
}
}
class Production{
private int product=0;
public synchronized void increase(){
if (product!=0){ //改为while循环
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//业务操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product++; //完成生产的业务操作
System.out.println("生产者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
//唤醒其他进程
this.notifyAll();
}
public synchronized void decrease(){
while(product==0){ //改为while循环
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务
product--; //完成生产的业务操作
System.out.println("消费者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
//唤醒
this.notifyAll();
}
}
Lock锁解决生产者消费者问题
我们通过Lock接口实现的ReentrantLock中有一个方法可以创建一个Condition的对象
我们进入Condition类,可以看到有一个await方法和signalAll方法
这两个方法就相当于synchronized锁中的wait和notifyAll方法
修改后的代码为
package com.kuang;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02pc {
public static void main(String[] args) {
Production production = new Production();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.increase();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.decrease();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.increase();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
production.decrease();
}
},"D").start();
}
}
class Production{
private int product=0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increase(){
lock.lock();
try {
if (product!=0){ //改为while循环
//等待
condition.await();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product++; //完成生产的业务操作
System.out.println("生产者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
//唤醒其他进程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
//业务操作
}
public void decrease(){
lock.lock();
try {
while(product==0){ //改为while循环
//等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务
product--; //完成生产的业务操作
System.out.println("消费者为:"+Thread.currentThread().getName()+"当前产品数量为:"+product);
//唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
通过Condition实现精准唤醒
我们知道signalAll是唤醒所有的沉睡进程,signal是随机唤醒队列中第一个进程,那我们有什么班法唤醒指定的进程呢?
这就需要我们利用Condition来实现精准唤醒
我们可以在一把锁中创建4个Condition对象,然后通过一个计数器判断该唤醒那个进程
代码如下
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
data.Print1();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
data.Print2();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
data.Print3();
}
}, "C").start();
}
}
class Data{
private int product=0;
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number=1;
public void Print1(){
lock.lock();
try {
while(number!=1){
//等待
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("AAAAAAAAAA");
number=2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void Print2(){
lock.lock();
try {
while(number!=2){
//等待
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number=3;
System.out.println("BBBBBBBBBBBB");
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void Print3(){
lock.lock();
try {
while(number!=3){
//等待
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number=1;
System.out.println("CCCCCCCCCC");
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}