網上發的多生產者和多消費者線程安全的文章已經比較多了,但倉庫通常放一個資源,比較少看到有多個資源的,因爲多個共享資源更容易出錯。並且以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:被告知包子賣完了
歡迎指正