多線程設計模式:生產(chǎn)者-消費者(Producer-Consumer)

場景

生產(chǎn)者生產(chǎn)數(shù)據(jù)奄妨,消費者消費數(shù)據(jù);
但是性能處理速度均有差異炊琉,因而需要一個中間隊列協(xié)調展蒂;

舉例

3個廚師做甜點,有3個吃貨來吃苔咪,如果廚師和吃貨一對一锰悼,廚師的生產(chǎn)速度和吃貨的吃飯速度均成成了短板,可能存在這樣一種不協(xié)調的場景团赏;

  • 慢性子廚師款待一個吃飯快的胖吃貨箕般,吃貨都要餓死了,只能死等舔清;
  • 急性子廚師款待一個吃飯慢的瘦子吃貨(比如我女朋友)丝里,吃貨吃不完,堆了一堆甜點

這個時候怎么辦比較好呢?
加一個櫥柜体谒,糕點生產(chǎn)好一個一個放在櫥柜里杯聚,吃貨排隊一個一個拿,這樣是不是好多了呢抒痒,櫥柜協(xié)調了廚師和吃貨的關系幌绍,減少了浪費;

角色

生產(chǎn)者 廚師
消費者 吃貨
數(shù)據(jù) 糕點
中間隊列 桌子

提升與思考

代碼放在最后故响,先講幾個問題深入大家理解

  1. 隊列可以有多種實現(xiàn)(Strategy Pattern)
  • 先放先出 FIFO
  • 后放先出 LIFO
  • 優(yōu)先級隊列 Priority Quene
  1. 為什么生產(chǎn)和消費要用多線程?
    這也是我在實際應用中感受最深的傀广,答案只有一個,單線程太耗時
    只有當多線程的效率提升可以抵消開發(fā)難度和性能消耗時才有必要用多線程
  2. 別忘記sychronized 和 notifyAll()
    否則其他線程一直等待彩届,不會繼續(xù)
  3. 生產(chǎn)線程和消費線程的數(shù)量匹配
    上述問題可以只有一個生產(chǎn)線程和一個消費線程(PIPE模式)
    當生產(chǎn)比較費時的時候生產(chǎn)線程多一些伪冰,消費耗時時(如網(wǎng)絡IO)多一些消費線程(一對多對應線程池的WorkThread設計模式)
  • 當廚師效率較低時(Thread.sleep(1000))
    面包總是被吃光,可以增加廚師或者減少吃貨

面包被吃貨吃光樟蠕,等待廚師生產(chǎn)
面包被吃貨吃光贮聂,等待廚師生產(chǎn)
面包被吃貨吃光靠柑,等待廚師生產(chǎn)
廚師乙 在桌子上放入了 廚師乙產(chǎn)生的第1個面包
吃貨甲 從桌子上取走了 廚師乙產(chǎn)生的第1個面包
面包被吃貨吃光,等待廚師生產(chǎn)
面包被吃貨吃光寂汇,等待廚師生產(chǎn)
廚師甲 在桌子上放入了 廚師甲產(chǎn)生的第2個面包
吃貨丙 從桌子上取走了 廚師甲產(chǎn)生的第2個面包
面包被吃貨吃光病往,等待廚師生產(chǎn)
廚師丙 在桌子上放入了 廚師丙產(chǎn)生的第3個面包
吃貨乙 從桌子上取走了 廚師丙產(chǎn)生的第3個面包
面包被吃貨吃光,等待廚師生產(chǎn)
廚師乙 在桌子上放入了 廚師乙產(chǎn)生的第4個面包
吃貨甲 從桌子上取走了 廚師乙產(chǎn)生的第4個面包
面包被吃貨吃光骄瓣,等待廚師生產(chǎn)
廚師丙 在桌子上放入了 廚師丙產(chǎn)生的第5個面包
吃貨丙 從桌子上取走了 廚師丙產(chǎn)生的第5個面包
面包被吃貨吃光,等待廚師生產(chǎn)
面包被吃貨吃光耍攘,等待廚師生產(chǎn)

  • 當廚師效率較高時(Thread.sleep(500)時)
    總是放不下榕栏,這時可以減少廚師或者增加吃貨

廚師丙 在桌子上放入了 廚師丙產(chǎn)生的第18個面包
桌子放滿了面包,不能再放了
桌子放滿了面包蕾各,不能再放了
吃貨丙 從桌子上取走了 廚師甲產(chǎn)生的第11個面包
廚師乙 在桌子上放入了 廚師乙產(chǎn)生的第7個面包
桌子放滿了面包扒磁,不能再放了
桌子放滿了面包,不能再放了
吃貨乙 從桌子上取走了 廚師丙產(chǎn)生的第16個面包
廚師丙 在桌子上放入了 廚師丙產(chǎn)生的第19個面包
桌子放滿了面包式曲,不能再放了
吃貨丙 從桌子上取走了 廚師丙產(chǎn)生的第18個面包
廚師甲 在桌子上放入了 廚師甲產(chǎn)生的第17個面包
桌子放滿了面包妨托,不能再放了
桌子放滿了面包,不能再放了
桌子放滿了面包吝羞,不能再放了
吃貨乙 從桌子上取走了 廚師乙產(chǎn)生的第7個面包
廚師甲 在桌子上放入了 廚師甲產(chǎn)生的第22個面包
桌子放滿了面包兰伤,不能再放了
桌子放滿了面包,不能再放了

所以中間隊列給我們提供了一個緩沖區(qū)钧排,用于協(xié)調生產(chǎn)者和消費者間的性能差異敦腔,控制互斥

總結:

  1. 線程的協(xié)調運行需要"放在中間的東西"
  2. 線程的互斥處理需要"保護中間的東西"
  3. 中間隊列使得兩端協(xié)調成為肯能

代碼

  • 生產(chǎn)者
/**
 * @author xuhe
 * @description 生產(chǎn)者線程
 * @date 2018/5/17
 */
public class ProducerThread extends Thread {

    public static int index;
    private String threadName;
    private DataChannel dataChannel;
    private Random random;

    public ProducerThread(String threadName, DataChannel dataChannel, long randomSeed) {
        this.threadName = threadName;
        this.dataChannel = dataChannel;
        this.random = new Random(randomSeed);
        setName(threadName);
    }


    @Override
    public void run() {
        try {
            while (true){
                Thread.sleep(random.nextInt(500));
                Data data= new Data();
                data.setName(String.format("%s產(chǎn)生的第%s個面包",threadName,getIndex()));
                dataChannel.put(data);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized int getIndex(){
        return ++index;
    }
}
  • 消費者
package com.microparticletech.producer_consumer;

import java.util.Random;

/**
 * @author xuhe
 * @description 消費者線程
 * @date 2018/5/17
 */
public class ConsumerThread extends Thread {

    private DataChannel dataChannel;
    private String threadName;
    private Random random;

    public ConsumerThread(String threadName,DataChannel dataChannel, long randomSeed) {
        this.threadName = threadName;
        this.dataChannel=dataChannel;
        this.random = new Random(randomSeed);
        setName(threadName);
    }


    @Override
    public void run() {
        System.out.println(threadName+"啟動");
        try {
            while (true){
                Data data=new Data();
                data=dataChannel.get();
                Thread.sleep(random.nextInt(1000));
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  • 數(shù)據(jù)

@lombok.Data
public class Data {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return
                "name='" + name + '\'';
    }
}
  • 中間隊列
public interface DataChannel {

     void  put(Data data) throws InterruptedException;

    Data get() throws InterruptedException;

}

public class DataChannelFIFOImpl implements DataChannel {
//先進先出的隊列實現(xiàn)
    private static final int CAPACITY=3;
    private Data[] dataArray = new Data[CAPACITY];
    private int head;
    private int tail;
    private int count;

    @Override
    public synchronized void put(Data data) throws InterruptedException {
        while (count>=CAPACITY){
            System.out.println("桌子放滿了面包,不能再放了");
            wait();
        }
        System.out.println(String.format("%s 在桌子上放入了 %s",Thread.currentThread().getName(),data.getName()));
        dataArray[tail]=data;
        tail=(tail+1)%CAPACITY;
        count++;
        notifyAll();
    }

    @Override
    public synchronized Data get() throws InterruptedException {
        while (count<=0){
            System.out.println("面包被吃貨吃光恨溜,等待廚師生產(chǎn)");
            wait();
        }
        Data data=dataArray[head];
        System.out.println(String.format("%s 從桌子上取走了 %s",Thread.currentThread().getName(),data.getName()));
        count--;
        head=(head+1)%CAPACITY;
        notifyAll();
        return data;
    }
}

  • 啟動類
public class MainTest {

    public static void main(String[] args) {
        int size=3;
        DataChannel channel = new DataChannelFIFOImpl();

        Thread producer1 = new ProducerThread("廚師甲" , channel, 31415);
        Thread producer2 = new ProducerThread("廚師乙" , channel, 92653);
        Thread producer3 = new ProducerThread("廚師丙" , channel, 58979);
        producer1.start();
        producer2.start();
        producer3.start();

        Thread consumer1 = new ConsumerThread("吃貨甲" , channel, 32384);
        Thread consumer2 = new ConsumerThread("吃貨乙" , channel, 62643);
        Thread consumer3 = new ConsumerThread("吃貨丙" , channel, 38327);
        consumer1.start();
        consumer2.start();
        consumer3.start();
        
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末符衔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子糟袁,更是在濱河造成了極大的恐慌判族,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项戴,死亡現(xiàn)場離奇詭異形帮,居然都是意外死亡,警方通過查閱死者的電腦和手機肯尺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門沃缘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人则吟,你說我怎么就攤上這事槐臀。” “怎么了氓仲?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵水慨,是天一觀的道長得糜。 經(jīng)常有香客問我,道長晰洒,這世上最難降的妖魔是什么朝抖? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮谍珊,結果婚禮上治宣,老公的妹妹穿的比我還像新娘。我一直安慰自己砌滞,他們只是感情好侮邀,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贝润,像睡著了一般绊茧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上打掘,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天华畏,我揣著相機與錄音,去河邊找鬼尊蚁。 笑死亡笑,一個胖子當著我的面吹牛,可吹牛的內容都是我干的枝誊。 我是一名探鬼主播况芒,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叶撒!你這毒婦竟也來了绝骚?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤祠够,失蹤者是張志新(化名)和其女友劉穎压汪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古瓤,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡止剖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了落君。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穿香。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绎速,靈堂內的尸體忽然破棺而出皮获,到底是詐尸還是另有隱情,我是刑警寧澤纹冤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布洒宝,位于F島的核電站购公,受9級特大地震影響,放射性物質發(fā)生泄漏雁歌。R本人自食惡果不足惜宏浩,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望靠瞎。 院中可真熱鬧比庄,春花似錦、人聲如沸乏盐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丑勤。三九已至,卻和暖如春吧趣,著一層夾襖步出監(jiān)牢的瞬間法竞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工强挫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岔霸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓俯渤,卻偏偏與公主長得像呆细,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子八匠,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容

  • 前言 《四柱特訓班講義》一書絮爷,是筆者根據(jù)2003年春舉辦的四柱特訓班講課記錄的基礎上整理出來的。它是以《四柱詳真》...
    小狐貍娃娃閱讀 11,860評論 1 29
  • 冬天的夜晚非常的冷梨树,小女孩兒獨自在大街上走著坑夯,沒人給她錢,也沒人給她吃的抡四,她就這樣柜蜈,孤零零的走著,在快天黑的時候...
    雨_Lyj閱讀 683評論 0 0
  • 很多職場新人都會遇到以下3個問題:感覺每天都很多事情要干,還不知道從何干起藻雪;所做的事情都很零碎秘噪,卻依然沒法按時完成...
    緣小異閱讀 2,052評論 0 1
  • 壽光世紀教育集團東城初中 馬宗國 “這種教學評的一致性研究和我們的課程標準校本化研究是相通的缆娃,給了我們很多...
    從心而覓閱讀 711評論 0 1
  • 1捷绒、指導型的學習與自我發(fā)現(xiàn)型的學習 自我發(fā)現(xiàn)型學習就是沒有老師指導的學習方式,學習者是立足于自然或世界贯要,來閱讀自我...
    飛鷹于凱閱讀 221評論 0 1