阻塞隊(duì)列 BlockingQueue
1. 前言
本節(jié)帶領(lǐng)大家認(rèn)識第三個(gè)常用的 Java 并發(fā)容器類之 BlockingQueue。
本節(jié)先介紹 BlockingQueue 工具類表達(dá)的概念和最基本用法,接著通過一個(gè)例子為大家解釋 BlockingQueue 工具類的使用場合,然后通過簡單的編碼實(shí)現(xiàn)此場景。
下面我們正式開始介紹吧。
2. 概念解釋
BlockingQueue 顧名思義,就是阻塞隊(duì)列。隊(duì)列的概念同我們?nèi)粘I钪械年?duì)列一樣。在計(jì)算機(jī)中,隊(duì)列具有先入先出的特征,不允許插隊(duì)的情況出現(xiàn)。在 Java 中,BlockingQueue 是一個(gè) interface 而非 class,它有很多實(shí)現(xiàn)類,如 ArrayBlockingQueue、LinkedBlockingQueue 等,這些實(shí)現(xiàn)類之間主要區(qū)別體現(xiàn)在存儲結(jié)構(gòu)或元素操作上,但入隊(duì)和出隊(duì)操作卻是類似的。
概念已經(jīng)了解了,BlockingQueue 工具類最基本的用法是怎樣的呢,我們以 LinkedBlockingQueue 實(shí)現(xiàn)類為例說明?看下面。
3. 基本用法
// 創(chuàng)建一個(gè) LinkedBlockingQueue 對象
LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue();
// 在不違反容量限制的情況下,可立即將指定元素插入此隊(duì)列,成功返回 true,當(dāng)無可用空間時(shí)候,返回 IllegalStateException 異常。
linkedBlockingQueue.add("car");
// 在不違反容量限制的情況下,可立即將指定元素插入此隊(duì)列,成功返回 true,當(dāng)無可用空間時(shí)候,返回 false。
linkedBlockingQueue.offer("car");
// 直接在隊(duì)列中插入元素,當(dāng)無可用空間時(shí)候,阻塞等待。
linkedBlockingQueue.put("car");
// 將給定元素在給定的時(shí)間內(nèi)設(shè)置到隊(duì)列中,如果設(shè)置成功返回 true,否則返回 false,超時(shí)后返回 fase。
linkedBlockingQueue.offer("car", 10, Timeunit.SECONDS);
// 獲取并移除隊(duì)列頭部的元素,無元素時(shí)候阻塞等待。
linkedBlockingQueue.take();
// 獲取并移除隊(duì)列頭部的元素,無元素時(shí)候阻塞等待指定時(shí)間。超時(shí)后返回 null。
linkedBlockingQueue.poll(10, Timeunit.SECONDS);
是不是很簡單,那 BlockingQueue 應(yīng)用在哪些場合比較合適呢?下面我們給出最常用的場景說明。
4. 常用場景
BlockingQueue 首先作為一個(gè)隊(duì)列,可以適用于任何需要隊(duì)列數(shù)據(jù)結(jié)構(gòu)的場合,其次其具有阻塞操作的特征,可用于線程間協(xié)同操作的場合。日常研發(fā)中,生產(chǎn)者消費(fèi)者模型常常使用 BlockingQueue 實(shí)現(xiàn)。我們通過一張圖了解其基本邏輯。
我們舉一個(gè)生活中汽車排隊(duì)加油的例子說明:每一個(gè)加油站臺就是一個(gè)阻塞隊(duì)列,汽車依次排隊(duì)進(jìn)入,先進(jìn)入的先出站。當(dāng)站臺滿了后繼車輛就需要排隊(duì)等待,當(dāng)前面的汽車加好油離開(出隊(duì))后,后面的汽車進(jìn)入(入隊(duì))開始加油。我們用 LinkedBlockingQueue 工具類實(shí)現(xiàn),請看下面代碼。
5. 場景案例
import lombok.SneakyThrows;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueTest {
// 創(chuàng)建一個(gè) LinkedBlockingQueue 對象,用于汽車排隊(duì)
private static LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>();
// 主線程
public static void main(String[] args) throws InterruptedException {
// 汽車
int carNumber = 1;
while(carNumber < 5) {
new Thread(new Runnable() {
@SneakyThrows
public void run() {
// 模擬用時(shí)
Thread.sleep(new Random().nextInt(1000));
// 汽車進(jìn)站排隊(duì)等待
linkedBlockingQueue.offer(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + ":已經(jīng)排隊(duì)進(jìn)入收費(fèi)站臺,等候收費(fèi)...");
}
}, "汽車" + carNumber++).start();
}
// 收費(fèi)員
new Thread(new Runnable() {
@SneakyThrows
public void run() {
while(true) {
// 模擬用時(shí)
Thread.sleep(new Random().nextInt(1000));
// 汽車過收費(fèi)后出站
String car = linkedBlockingQueue.take();
System.out.println(Thread.currentThread().getName() + ":汽車" + car + "已經(jīng)收費(fèi)完畢離開收費(fèi)站臺");
}
}
}, "收費(fèi)員").start();
// 信息展示
new Thread(new Runnable() {
@SneakyThrows
public void run() {
while(true) {
Thread.sleep(1000);
// 實(shí)時(shí)公示當(dāng)前車流排隊(duì)情況
int howMany = linkedBlockingQueue.size();
System.out.println(Thread.currentThread().getName() + ":當(dāng)前還" + howMany + "輛在等候過站");
}
}
}, "大屏").start();
Thread.sleep(1000000);
}
}
運(yùn)行上面代碼,我們觀察一下運(yùn)行結(jié)果。
汽車1:已經(jīng)排隊(duì)進(jìn)入收費(fèi)站臺,等候收費(fèi)...
汽車2:已經(jīng)排隊(duì)進(jìn)入收費(fèi)站臺,等候收費(fèi)...
汽車4:已經(jīng)排隊(duì)進(jìn)入收費(fèi)站臺,等候收費(fèi)...
收費(fèi)員:汽車汽車1已經(jīng)收費(fèi)完畢離開收費(fèi)站臺
汽車3:已經(jīng)排隊(duì)進(jìn)入收費(fèi)站臺,等候收費(fèi)...
大屏:當(dāng)前還3輛在等候過站
收費(fèi)員:汽車汽車2已經(jīng)收費(fèi)完畢離開收費(fèi)站臺
大屏:當(dāng)前還2輛在等候過站
收費(fèi)員:汽車汽車4已經(jīng)收費(fèi)完畢離開收費(fèi)站臺
收費(fèi)員:汽車汽車3已經(jīng)收費(fèi)完畢離開收費(fèi)站臺
大屏:當(dāng)前還0輛在等候過站
觀察結(jié)果,和我們的預(yù)期一致。
6. 小結(jié)
本節(jié)通過一個(gè)簡單的例子,介紹了 BlockingQueue 接口以及一個(gè)具體的實(shí)現(xiàn)類 LinkedBlockingQueue 的使用場景和基本用法,其他實(shí)現(xiàn)了 BlockingQueue 接口的實(shí)現(xiàn)類都類似。希望大家在學(xué)習(xí)過程中,多思考勤練習(xí),早日掌握之。