[Java併發編程實戰] 阻塞隊列 BlockingQueue(含代碼,生產者-消費者模型)

見賢思齊焉,見不賢而內自省也。—《論語》

PS: 如果覺得本文有用的話,請幫忙點贊,留言評論支持一下哦,您的支持是我最大的動力!謝謝啦~

Java5.0 增加了兩種新的容器類型,它們是指:Queue 和 BlockingQueue。Queue 用來臨時保存一組等待處理的元素。BlockingQueue 擴張了 Queue 接口,增加了可阻塞的插入和獲取等操作。

BlockingQueue 通常運用於一個線程生產對象放入隊列,另一個線程從隊列獲取對象並消費,這是典型的生產者消費者模型。

這裏寫圖片描述這裏寫圖片描述

生產者線程持續生產新對象並插入隊列,如果隊列已滿,那麼插入對象的操作會一直阻塞,直到隊列中出現可用的空間。
消費者線程持續從隊列獲取對象,如果隊列爲空,那麼獲取操作會一直阻塞,直到隊列中出現可用的新對象。BlockingQueue 簡化了生產者-消費者設計的實現過程,它支持任意數量的生產者和消費者。

BlockingQueue 的核心方法:

這裏寫圖片描述這裏寫圖片描述

offer(E): 向隊列插入元素,並返回插入成功與否。本方法不阻塞當前執行線程。

put(E) : 向隊列插入元素,如果隊列已滿,則會阻塞當前線程直至元素加入隊列。

take() : 獲取隊列的首位元素,如果隊列爲空,則阻塞當前線程直至隊列有元素並取走。

poll():獲取隊列首個元素,指定時間內一旦數據可取,則立即返回;否則返回失敗。

remove(E):刪除隊列中的元素,返回成功與否。

BlockingQueue 的實現

BlockingQueue是一個接口,所以你必須使用它的實現來使用它。它的實現包括以下幾個:

  • ArrayBlockingQueue:基於數組實現的有界隊列(FIFO),使用一把全局鎖並行對 queue 讀寫操作,同時使用兩個 Condition 阻塞隊列爲空時的取操作和隊列爲滿時的寫操作。

  • LinkedBlockingQueue:基於已鏈接節點的,範圍上限爲 Integer.MAX_VALUE 的 blocking queue(FIFO)。主要操作 put 和 take 都是阻塞的。

  • DelayQueue:當指定的延遲時間到了,才能夠從隊列中獲取元素。它沒有大小限制,因此插入元素時不會阻塞,而只有獲取元素時纔會被阻塞。它的用法可以參考下面兩篇博客:
    http://www.cnblogs.com/jobs/archive/2007/04/27/730255.html,
    http://www.cnblogs.com/sunzhenchao/p/3515085.html

  • PriorityBlockingQueue: 基於優先級的阻塞隊列,但需要注意的是PriorityBlockingQueue並不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。

  • SynchronoutQueue:它的內部同時只能夠容納單個元素。如果該隊列已有一個元素的話,試圖向隊列插入一個新元素線程會阻塞,知道另一個線程將該元素從隊列中拿走。同樣,如果該隊列爲空,試圖向隊列中抽取一個元素的線程將會阻塞,知道另一個線程向隊列中插入一個新的元素。SynchronousQueue適合一對一的匹配場景,沒有容量,無法緩存。它的用法強烈推薦博客: 
    http://www.cnblogs.com/leesf456/p/5560362.html

BlockingQueue的使用

這是一個使用 BlockingQueue 的例子,本例使用 ArrayBlockingQueue 實現。首先,BlockingQueueTest 創建一個生產者線程 Procucer, 把字符存放進共享隊列。然後創建三個消費者線程 Consumer,把字符串從隊列中取出。Consumer 取到最後一個字符串時,中斷所有消費者線程,結束程序。

 1import java.util.ArrayList;
2import java.util.Iterator;
3import java.util.List;
4import java.util.Vector;
5import java.util.concurrent.ArrayBlockingQueue;
6import java.util.concurrent.BlockingQueue;
7
8public class BlockingQueueTest {
9    //隊列容量
10    private static final int SIZE = 5;
11    private static final int CONSUMER_SIZE = 3;
12    //消費者線程退出標誌
13    private static String endString = "num:" + (SIZE*2-1);
14    //存放消費者線程
15    private static List list = new ArrayList<Thread>();
16
17    public static void main(String[] args) throws Exception{
18        //創建固定長度的阻塞隊列
19        BlockingQueue q = new ArrayBlockingQueue<String>(SIZE);
20
21        //創建生產者
22        Producer producer = new Producer(q);
23        //啓動生產者線程,生產對象
24        producer.start();
25
26        //啓動消費者線程,獲取隊列對象
27        for(int i = 0; i < CONSUMER_SIZE; i++) {
28            list.add(new Consumer(q));
29            ((Thread) list.get(i)).start();
30        }
31    }
32
33    //中斷線程
34    public static void shutDownThread() {
35        for(int i = 0; i < CONSUMER_SIZE; i++) {
36            ((Thread) list.get(i)).interrupt();
37        }
38    }
39
40    static class Producer extends Thread{
41
42        private BlockingQueue queue = null;
43
44        public Producer(BlockingQueue q) {
45            this.queue = q;
46        }
47
48        @Override
49        public void run() {
50            // TODO Auto-generated method stub
51            try {
52                //生產10個對象,放進隊列
53                for(int i = 0; i < SIZE*2; i++) {
54                    String str = "num:" + i;
55                    System.out.println(Thread.currentThread().getName() +":"+"IN: " + str);
56                    queue.put(str);
57                    Thread.sleep(100);
58                }
59            } catch (InterruptedException e) {
60                // TODO Auto-generated catch block
61                e.printStackTrace();
62            }
63
64        }
65    }
66
67    //消費者線程
68    static class Consumer extends Thread{
69
70        private BlockingQueue queue = null;
71
72        public Consumer(BlockingQueue q) {
73            this.queue = q;
74        }
75
76        @Override
77        public void run() {
78            // TODO Auto-generated method stub
79            try {
80                //獲取隊列的元素,隊列爲空時會阻塞
81                while(true) {
82                    String str = (String) queue.take();
83                    System.out.println(Thread.currentThread().getName() + ":" + "OUT " + str);
84                    //已經取出最後一個元素,消費者線程應該退出,否則程序一直在運行
85                    if(str.equals(endString)) {
86                        shutDownThread();//中斷線程
87                    }
88                }
89            } catch (InterruptedException e) {
90                // TODO Auto-generated catch block
91                e.printStackTrace();
92            }
93        }
94    }
95}

執行結果:

這裏寫圖片描述這裏寫圖片描述

總結

java.util.concurrent 中實現的阻塞隊列包含了足夠的同步機制,從而能夠安全的將對象從生產者線程發佈到消費者線程。對於可變對象,生產者-消費者模型,把對象的所有權安全的從生產者交付給消費者。轉移後,消費者線程獲得這個對象的所有權(獨佔訪問權,可以任意修改它),並且生產者線程不會再訪問它。同時,阻塞隊列負責所有的控制流,使得消費者生產者的代碼更加簡單和清晰。

本文完畢,如對你有幫助,請關注我,謝謝~

參考

  1. https://blog.csdn.net/defonds/article/details/44021605/

  2. http://www.cnblogs.com/leesf456/p/5428630.html

  3. 《併發編程實戰》


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