什麼是阻塞佇列?如何使用阻塞佇列來實現生產者-消費者模型?

搜雲庫技術團隊發表於2018-03-14

什麼是阻塞佇列?

阻塞佇列是一個在佇列基礎上又支援了兩個附加操作的佇列。

2個附加操作:

支援阻塞的插入方法:佇列滿時,佇列會阻塞插入元素的執行緒,直到佇列不滿。

支援阻塞的移除方法:佇列空時,獲取元素的執行緒會等待佇列變為非空。

阻塞佇列的應用場景

阻塞佇列常用於生產者和消費者的場景,生產者是向佇列裡新增元素的執行緒,消費者是從佇列裡取元素的執行緒。簡而言之,阻塞佇列是生產者用來存放元素、消費者獲取元素的容器。

幾個方法

在阻塞佇列不可用的時候,上述2個附加操作提供了四種處理方法

方法\處理方式 丟擲異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

JAVA裡的阻塞佇列

JDK 7 提供了7個阻塞佇列,如下

1、ArrayBlockingQueue 陣列結構組成的有界阻塞佇列。

此佇列按照先進先出(FIFO)的原則對元素進行排序,但是預設情況下不保證執行緒公平的訪問佇列,即如果佇列滿了,那麼被阻塞在外面的執行緒對佇列訪問的順序是不能保證執行緒公平(即先阻塞,先插入)的。

2、LinkedBlockingQueue一個由連結串列結構組成的有界阻塞佇列

此佇列按照先出先進的原則對元素進行排序

3、PriorityBlockingQueue 支援優先順序的無界阻塞佇列

4、DelayQueue 支援延時獲取元素的無界阻塞佇列,即可以指定多久才能從佇列中獲取當前元素

5、SynchronousQueue不儲存元素的阻塞佇列,每一個put必須等待一個take操作,否則不能繼續新增元素。並且他支援公平訪問佇列。

6、LinkedTransferQueue由連結串列結構組成的無界阻塞TransferQueue佇列。相對於其他阻塞佇列,多了tryTransfer和transfer方法

transfer方法

如果當前有消費者正在等待接收元素(take或者待時間限制的poll方法),transfer可以把生產者傳入的元素立刻傳給消費者。如果沒有消費者等待接收元素,則將元素放在佇列的tail節點,並等到該元素被消費者消費了才返回。

tryTransfer方法

用來試探生產者傳入的元素能否直接傳給消費者。,如果沒有消費者在等待,則返回false。和上述方法的區別是該方法無論消費者是否接收,方法立即返回。而transfer方法是必須等到消費者消費了才返回。

7、LinkedBlockingDeque連結串列結構的雙向阻塞佇列,優勢在於多執行緒入隊時,減少一半的競爭。

如何使用阻塞佇列來實現生產者-消費者模型?

通知模式實現:所謂通知模式,就是當生產者往滿的佇列裡新增元素時會阻塞住生產者,當消費者消費了一個佇列中的元素後,會通知生產者當前佇列可用。

使用BlockingQueue解決生產者消費者問題

為什麼BlockingQueue適合解決生產者消費者問題

任何有效的生產者-消費者問題解決方案都是通過控制生產者put()方法(生產資源)和消費者take()方法(消費資源)的呼叫來實現的,一旦你實現了對方法的阻塞控制,那麼你將解決該問題。

Java通過BlockingQueue提供了開箱即用的支援來控制這些方法的呼叫(一個執行緒建立資源,另一個消費資源)。java.util.concurrent包下的BlockingQueue介面是一個執行緒安全的可用於存取物件的佇列。

BlockingQueue是一種資料結構,支援一個執行緒往裡存資源,另一個執行緒從裡取資源。這正是解決生產者消費者問題所需要的,那麼讓我們開始解決該問題吧。

生產者

以下程式碼用於生產者執行緒

package io.ymq.example.thread;

import java.util.concurrent.BlockingQueue;

/**
 * 描述:生產者
 *
 * @author yanpenglei
 * @create 2018-03-14 15:52
 **/
class Producer implements Runnable {

    protected BlockingQueue<Object> queue;

    Producer(BlockingQueue<Object> theQueue) {
        this.queue = theQueue;
    }

    public void run() {
        try {
            while (true) {
                Object justProduced = getResource();
                queue.put(justProduced);
                System.out.println("生產者資源佇列大小= " + queue.size());
            }
        } catch (InterruptedException ex) {
            System.out.println("生產者 中斷");
        }
    }

    Object getResource() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException ex) {
            System.out.println("生產者 讀 中斷");
        }
        return new Object();
    }
}
複製程式碼

消費者

以下程式碼用於消費者執行緒

package io.ymq.example.thread;

import java.util.concurrent.BlockingQueue;

/**
 * 描述: 消費者
 *
 * @author yanpenglei
 * @create 2018-03-14 15:54
 **/
class Consumer implements Runnable {
    protected BlockingQueue<Object> queue;

    Consumer(BlockingQueue<Object> theQueue) {
        this.queue = theQueue;
    }

    public void run() {
        try {
            while (true) {
                Object obj = queue.take();
                System.out.println("消費者 資源 佇列大小 " + queue.size());
                take(obj);
            }
        } catch (InterruptedException ex) {
            System.out.println("消費者 中斷");
        }
    }

    void take(Object obj) {
        try {
            Thread.sleep(100); // simulate time passing
        } catch (InterruptedException ex) {
            System.out.println("消費者 讀 中斷");
        }
        System.out.println("消費物件 " + obj);
    }
}
複製程式碼

測試該解決方案是否執行正常

package io.ymq.example.thread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 描述: 測試
 *
 * @author yanpenglei
 * @create 2018-03-14 15:58
 **/
public class ProducerConsumerExample {

    public static void main(String[] args) throws InterruptedException {

        int numProducers = 4;
        int numConsumers = 3;

        BlockingQueue<Object> myQueue = new LinkedBlockingQueue<Object>(5);

        for (int i = 0; i < numProducers; i++) {
            new Thread(new Producer(myQueue)).start();
        }

        for (int i = 0; i < numConsumers; i++) {
            new Thread(new Consumer(myQueue)).start();
        }

        Thread.sleep(1000);

        System.exit(0);
    }
}
複製程式碼

執行結果

生產者資源佇列大小= 1
生產者資源佇列大小= 1
消費者 資源 佇列大小 1
生產者資源佇列大小= 1
消費者 資源 佇列大小 1
消費者 資源 佇列大小 1
生產者資源佇列大小= 1
生產者資源佇列大小= 3
消費物件 java.lang.Object@1e1aa52b
生產者資源佇列大小= 2
生產者資源佇列大小= 5
消費物件 java.lang.Object@6e740a76
消費物件 java.lang.Object@697853f6

......

消費物件 java.lang.Object@41a10cbc
消費物件 java.lang.Object@4963c8d1
消費者 資源 佇列大小 5
生產者資源佇列大小= 5
生產者資源佇列大小= 5
消費者 資源 佇列大小 4
消費物件 java.lang.Object@3e49c35d
消費者 資源 佇列大小 4
生產者資源佇列大小= 5
複製程式碼

從輸出結果中,我們可以發現佇列大小永遠不會超過5,消費者執行緒消費了生產者生產的資源

Contact

  • 作者:鵬磊
  • 出處:www.ymq.io
  • 版權歸作者所有,轉載請註明出處
  • Wechat:關注公眾號,搜雲庫,專注於開發技術的研究與知識分享

關注公眾號-搜雲庫
搜雲庫

相關文章