网上发的多生产者和多消费者线程安全的文章已经比较多了,但仓库通常放一个资源,比较少看到有多个资源的,因为多个共享资源更容易出错。并且以synchronized加锁的方式比较多,自己尝试使用Lock和Condition方式来实现,花了不少时间。目前测试之后没有发现问题了。放在网上来是让各位看官看看有没有可以优化或者还有bug的地方。文章是原创,转载请声明出处。
直接上代码:
package com.itheima.day13.lession;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 三个伙计(生产者),三个吃货(消费者)。
* 伙计不停的生产,吃货不停的消费
* 包子铺的仓库最多放5个包子,每天生产和消费20个包子
*
* @author NewBoy
* @since 2020/3/4
*/
public class Demo15BaoZi {
public static void main(String[] args) {
//创建一个容器,放做好的包子,最多放5个包子,每天最多生产和销售20个包子
LinkedList<String> buns = new LinkedList<>();
//创建线程共用的锁
Lock lock = new ReentrantLock();
// 得到2个监视器:一个给伙计(生产者),一个给吃货(消费者)
Condition huoji = lock.newCondition(); //伙计的监视器
Condition chihuo = lock.newCondition(); //吃货的监视器
//创建生产者的线程任务
BaoZiPu baoZiPu = new BaoZiPu(buns, lock, huoji, chihuo);
//创建消费者的线程任务
ChiHuo chiHuo = new ChiHuo(buns, lock, huoji, chihuo);
//多个生产者,多个消费者
for (int i = 1; i <= 3; i++) {
new Thread(baoZiPu, "伙计" + i).start();
new Thread(chiHuo, "吃货" + i).start();
}
}
}
/**
* 包子铺生产者的任务
*/
class BaoZiPu implements Runnable {
//一共生产20个,计数由多个线程共享,为了避免线程安全问题
private AtomicInteger count = new AtomicInteger(0);
//成员变量
private LinkedList<String> buns;
private Lock lock;
private Condition huoji;
private Condition chihuo;
//构造方法传入全部共享的参数
public BaoZiPu(LinkedList<String> buns, Lock lock, Condition huoji, Condition chihuo) {
this.buns = buns;
this.lock = lock;
this.huoji = huoji;
this.chihuo = chihuo;
}
@Override
public void run() {
while (true) {
try {
//随机休眠一段时间,模拟做包子需要花时间
Thread.sleep(new Random().nextInt(100)); //释放CPU,不会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//同步代码块,上锁
lock.lock();
//最多可以放5个,生产前检查是否还有放包子的空位
if (buns.size() == 5) {
try {
System.out.println(Thread.currentThread().getName() + ":发现仓库满了,呼叫吃货");
//通知消费者消费
chihuo.signal();
//生产者线程等待,同时释放锁和CPU
huoji.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断今天的包子是否生产完了
if (count.get() == 20) {
System.out.println(Thread.currentThread().getName() + ":被告知今天工作做完了");
huoji.signalAll(); //退出前唤醒其它生产者线程
//这里break只是当前线程结束循环,其它线程还在执行循环
break;
}
//如果不到20个,就继续生产
count.getAndIncrement(); //计数加1
buns.add("包子" + count); //放入仓库
//输出仓库现在的信息
System.out.println(Thread.currentThread().getName() + ":做了包子,全部人共做了" + count + "个包子,仓库有" + buns.size() + "个包子:" + buns);
} finally {
//上面的break会导致没有解锁,放在要放在finally中
lock.unlock();
}
}
}
}
/**
* 吃货,消费者
*/
class ChiHuo implements Runnable {
//多个消费者会有线程安全问题
private AtomicInteger count = new AtomicInteger(0);
//成员变量
private LinkedList<String> buns;
private Lock lock;
private Condition huoji;
private Condition chihuo;
//构造方法传入全部共享的参数
public ChiHuo(LinkedList<String> buns, Lock lock, Condition huoji, Condition chihuo) {
this.buns = buns;
this.lock = lock;
this.huoji = huoji;
this.chihuo = chihuo;
}
@Override
public void run() {
while (true) {
try {
//随机休眠一段时间,模拟做吃包子需要花时间
Thread.sleep(new Random().nextInt(100)); //释放CPU,不会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//同步代码块上锁
lock.lock();
//如果已经卖了20个,就退出循环
if (count.get() == 20) {
System.out.println(Thread.currentThread().getName() + ":被告知包子卖完了");
//退出前唤醒其它消费者线程,不然会有线程一直等待
chihuo.signalAll();
break;
}
//判断仓库是否空了
if (buns.size() == 0) {
try {
System.out.println(Thread.currentThread().getName() + ":发现仓库空了,叫人生产");
//通知生产者
huoji.signal();
//消费者等待
chihuo.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果仓库有包子就继续,唤醒后继续吃包子,这里要判断是否为空,多线程会出现0元素不存在的问题
if (!buns.isEmpty()) {
//删除第1个元素
buns.removeFirst();
count.getAndIncrement(); //加1
//输出仓库现有的信息
System.out.println(Thread.currentThread().getName() + ":吃了包子,全部人共吃了" + count + "个包子,仓库有" + buns.size() + "个包子:" + buns);
}
} finally {
//上面的break会导致没有解锁
lock.unlock();
}
}
}
}
运行结果如下:
伙计1:做了包子,全部人共做了1个包子,仓库有1个包子:[包子1]
吃货3:吃了包子,全部人共吃了1个包子,仓库有0个包子:[]
吃货1:发现仓库空了,叫人生产
伙计2:做了包子,全部人共做了2个包子,仓库有1个包子:[包子2]
吃货2:吃了包子,全部人共吃了2个包子,仓库有0个包子:[]
吃货3:发现仓库空了,叫人生产
伙计2:做了包子,全部人共做了3个包子,仓库有1个包子:[包子3]
伙计3:做了包子,全部人共做了4个包子,仓库有2个包子:[包子3, 包子4]
伙计1:做了包子,全部人共做了5个包子,仓库有3个包子:[包子3, 包子4, 包子5]
吃货2:吃了包子,全部人共吃了3个包子,仓库有2个包子:[包子4, 包子5]
吃货2:吃了包子,全部人共吃了4个包子,仓库有1个包子:[包子5]
伙计2:做了包子,全部人共做了6个包子,仓库有2个包子:[包子5, 包子6]
伙计1:做了包子,全部人共做了7个包子,仓库有3个包子:[包子5, 包子6, 包子7]
伙计3:做了包子,全部人共做了8个包子,仓库有4个包子:[包子5, 包子6, 包子7, 包子8]
伙计3:做了包子,全部人共做了9个包子,仓库有5个包子:[包子5, 包子6, 包子7, 包子8, 包子9]
吃货2:吃了包子,全部人共吃了5个包子,仓库有4个包子:[包子6, 包子7, 包子8, 包子9]
伙计1:做了包子,全部人共做了10个包子,仓库有5个包子:[包子6, 包子7, 包子8, 包子9, 包子10]
伙计3:发现仓库满了,呼叫吃货
吃货1:吃了包子,全部人共吃了6个包子,仓库有4个包子:[包子7, 包子8, 包子9, 包子10]
伙计2:做了包子,全部人共做了11个包子,仓库有5个包子:[包子7, 包子8, 包子9, 包子10, 包子11]
吃货2:吃了包子,全部人共吃了7个包子,仓库有4个包子:[包子8, 包子9, 包子10, 包子11]
伙计1:做了包子,全部人共做了12个包子,仓库有5个包子:[包子8, 包子9, 包子10, 包子11, 包子12]
伙计2:发现仓库满了,呼叫吃货
吃货3:吃了包子,全部人共吃了8个包子,仓库有4个包子:[包子9, 包子10, 包子11, 包子12]
伙计1:做了包子,全部人共做了13个包子,仓库有5个包子:[包子9, 包子10, 包子11, 包子12, 包子13]
吃货1:吃了包子,全部人共吃了9个包子,仓库有4个包子:[包子10, 包子11, 包子12, 包子13]
吃货3:吃了包子,全部人共吃了10个包子,仓库有3个包子:[包子11, 包子12, 包子13]
吃货2:吃了包子,全部人共吃了11个包子,仓库有2个包子:[包子12, 包子13]
吃货2:吃了包子,全部人共吃了12个包子,仓库有1个包子:[包子13]
吃货3:吃了包子,全部人共吃了13个包子,仓库有0个包子:[]
吃货2:发现仓库空了,叫人生产
伙计3:做了包子,全部人共做了14个包子,仓库有1个包子:[包子14]
伙计3:做了包子,全部人共做了15个包子,仓库有2个包子:[包子14, 包子15]
吃货1:吃了包子,全部人共吃了14个包子,仓库有1个包子:[包子15]
伙计1:做了包子,全部人共做了16个包子,仓库有2个包子:[包子15, 包子16]
伙计1:做了包子,全部人共做了17个包子,仓库有3个包子:[包子15, 包子16, 包子17]
吃货3:吃了包子,全部人共吃了15个包子,仓库有2个包子:[包子16, 包子17]
吃货1:吃了包子,全部人共吃了16个包子,仓库有1个包子:[包子17]
伙计3:做了包子,全部人共做了18个包子,仓库有2个包子:[包子17, 包子18]
伙计1:做了包子,全部人共做了19个包子,仓库有3个包子:[包子17, 包子18, 包子19]
吃货3:吃了包子,全部人共吃了17个包子,仓库有2个包子:[包子18, 包子19]
吃货3:吃了包子,全部人共吃了18个包子,仓库有1个包子:[包子19]
吃货1:吃了包子,全部人共吃了19个包子,仓库有0个包子:[]
伙计3:做了包子,全部人共做了20个包子,仓库有1个包子:[包子20]
伙计3:被告知今天工作做完了
伙计2:被告知今天工作做完了
吃货1:吃了包子,全部人共吃了20个包子,仓库有0个包子:[]
吃货1:被告知包子卖完了
伙计1:被告知今天工作做完了
吃货3:被告知包子卖完了
吃货2:被告知包子卖完了
欢迎指正