使用Lock和Condition完成多生产者和多消费者问题

网上发的多生产者和多消费者线程安全的文章已经比较多了,但仓库通常放一个资源,比较少看到有多个资源的,因为多个共享资源更容易出错。并且以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:被告知包子卖完了

欢迎指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章