Java併發基礎九:併發協作(生產者消費者模型)

對於多線程程序來說,不管任何編程語言,生產者和消費者模型都是最經典的。就像學習每一門編程語言一樣,Hello World!都是最經典的例子。

實際上,準確說應該是“生產者-消費者-倉儲”模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。
對於此模型,應該明確一下幾點:
1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
3、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

Demo實列:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author lishujiang
 * @date 2020/4/11 15:11
 */
public class ProducerConsumer {
    //代表生產的商品數量
    private static AtomicInteger count = new AtomicInteger(0);

    /**
     * 消費者
     */
    class Consumer implements Runnable {
        private String name;
        private Storage s = null;

        public Consumer(String name, Storage s) {
            this.name = name;
            this.s = s;
        }

        public void run() {
            try {
                while (true) {
                    Thread.sleep(5001);
                    System.out.println(name + "準備消費產品.");
                    Product product = s.pop();
                    System.out.println(name + "已消費(" + product.toString() + ").");
                    System.out.println("===============");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生產者
     */
    class Producer implements Runnable {
        private String name;
        private Storage s = null;

        public Producer(String name, Storage s) {
            this.name = name;
            this.s = s;
        }

        public void run() {
            try {
                while (true) {
                    Product product = new Product(count.incrementAndGet()); // 產生產品
                    System.out.println(name + "準備生產(" + product.toString() + ").");
                    s.push(product);
                    System.out.println(name + "已生產(" + product.toString() + ").");
                    System.out.println("===============");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }

        }
    }

    /**
     * 倉庫容量默認設置爲10,阻塞隊列生產消費線程安全
     */
    public class Storage {
        BlockingQueue<Product> queues = new LinkedBlockingQueue<Product>(10);

        /**
         * 生產
         *
         * @param p 產品
         * @throws InterruptedException
         */
        public void push(Product p) throws InterruptedException {
            queues.put(p);
        }

        /**
         * 消費
         *
         * @return 產品
         * @throws InterruptedException
         */
        public Product pop() throws InterruptedException {
            return queues.take();
        }
    }

    /**
     * 產品
     */
    public class Product {
        private int id;

        public Product(int id) {
            this.id = id;
        }

        public String toString() {// 重寫toString方法
            return "產品:" + this.id;
        }
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Storage s = pc.new Storage();

        ExecutorService service = Executors.newCachedThreadPool();
        Producer p = pc.new Producer("張三", s);
        Producer p2 = pc.new Producer("李四", s);
        Consumer c = pc.new Consumer("王五", s);
        Consumer c2 = pc.new Consumer("老劉", s);
        Consumer c3 = pc.new Consumer("老林", s);
        service.submit(p);
        service.submit(p2);
        service.submit(c);
//        service.submit(c2);
//        service.submit(c3);
    }
}

運行結果

張三準備生產(產品:1).
張三已生產(產品:1).
===============
李四準備生產(產品:2).
李四已生產(產品:2).
===============
張三準備生產(產品:3).
張三已生產(產品:3).
===============
李四準備生產(產品:4).
李四已生產(產品:4).
===============
張三準備生產(產品:5).
張三已生產(產品:5).
===============
李四準備生產(產品:6).
李四已生產(產品:6).
===============
李四準備生產(產品:7).
李四已生產(產品:7).
===============
張三準備生產(產品:8).
張三已生產(產品:8).
===============
李四準備生產(產品:9).
李四已生產(產品:9).
===============
張三準備生產(產品:10).
張三已生產(產品:10).
===============
李四準備生產(產品:11).
張三準備生產(產品:12).
王五準備消費產品.
李四已生產(產品:11).
===============
王五已消費(產品:1).
===============
李四準備生產(產品:13).

阻塞隊列BlockingQueue用法

多線程環境中,通過隊列可以很容易實現數據共享,比如經典的“生產者”和“消費者”模型中,通過隊列可以很便利地實現兩者之間的數據共享。

假設我們有若干生產者線程,另外又有若干個消費者線程。如果生產者線程需要把準備好的數據共享給消費者線程,利用隊列的方式來傳遞數據,就可以很方便地解決他們之間的數據共享問題。但如果生產者和消費者在某個時間段內,萬一發生數據處理速度不匹配的情況呢?理想情況下,如果生產者產出數據的速度大於消費者消費的速度,並且當生產出來的數據累積到一定程度的時候,那麼生產者必須暫停等待一下(阻塞生產者線程),以便等待消費者線程把累積的數據處理完畢,反之亦然。

然而,在concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。好在此時,強大的concurrent包橫空出世了,而他也給我們帶來了強大的BlockingQueue。(在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒)

BlockingQueue的兩個常見阻塞場景:
1、當隊列中沒有數據的情況下,消費者端的所有線程都會被自動阻塞(掛起),直到有數據放入隊列。
2、當隊列中填滿數據的情況下,生產者端的所有線程都會被自動阻塞(掛起),直到隊列中有空閒的位置,線程被自動喚醒。

這也是我們在多線程環境下,爲什麼需要BlockingQueue的原因。作爲BlockingQueue的使用者,我們再也不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都給你一手包辦了。

 

阻塞隊列參考:https://www.cnblogs.com/linjiqin/p/5130559.html

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