使用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:被告知包子賣完了

歡迎指正

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