Java多線程案例之阻塞隊(duì)列詳解
一.阻塞隊(duì)列介紹
1.1阻塞隊(duì)列特性
阻塞隊(duì)列特性:
一.安全性
二.產(chǎn)生阻塞效果
阻塞隊(duì)列是一種特殊的隊(duì)列. 也遵守 “先進(jìn)先出” 的原則.阻塞隊(duì)列能是一種線程安全的數(shù)據(jù)結(jié)構(gòu), 并且具有以下特性:
- 當(dāng)隊(duì)列滿的時(shí)候, 繼續(xù)入隊(duì)列就會(huì)阻塞, 直到有其他線程從隊(duì)列中取走元素.
- 當(dāng)隊(duì)列空的時(shí)候, 繼續(xù)出隊(duì)列也會(huì)阻塞, 直到有其他線程往隊(duì)列中插入元素.
阻塞隊(duì)列的一個(gè)典型應(yīng)用場(chǎng)景就是 “生產(chǎn)者消費(fèi)者模型”. 這是一種非常典型的開發(fā)模型.
1.2阻塞隊(duì)列的優(yōu)點(diǎn)
我們可以將阻塞隊(duì)列比做成"生產(chǎn)者"和"消費(fèi)者"的"交易平臺(tái)".
我們可以把這個(gè)模型來比做成"包餃子"
A 的作用是搟餃子皮,也就是"生產(chǎn)者"
B 的作用是包餃子,也就是"消費(fèi)者"
X 的作用一個(gè)當(dāng)作放搟好餃子皮的一個(gè)盤中,也就是阻塞隊(duì)列
這樣我們根據(jù)A,B,X可以想象以下場(chǎng)景
場(chǎng)景一:
當(dāng)A搟餃子皮的速度過快,X被A的桿好餃子皮放滿了,這樣A就需要停止搟餃子皮這一個(gè)操作,這時(shí)只能等待B來利用A提供的餃子皮包餃子后X所空出的空間,來給A提供生產(chǎn)的環(huán)境
場(chǎng)景二:
當(dāng)B包餃子的速度過快,X被B的包餃子所用的餃子皮用空,這樣B就需要停止包餃子這一個(gè)操作,這時(shí)只能等待A提供的餃子皮包餃子后X所存在餃子皮,來給B提供消費(fèi)的環(huán)境
二.生產(chǎn)者消費(fèi)者模型
生產(chǎn)者消費(fèi)者模式就是通過一個(gè)容器來解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問題
生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過阻塞隊(duì)列來進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取
(1) 阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力.
比如在 “秒殺” 場(chǎng)景下, 服務(wù)器同一時(shí)刻可能會(huì)收到大量的支付請(qǐng)求. 如果直接處理這些支付請(qǐng)求,服務(wù)器可能扛不住(每個(gè)支付請(qǐng)求的處理都需要比較復(fù)雜的流程). 這個(gè)時(shí)候就可以把這些請(qǐng)求都放到一個(gè)阻塞隊(duì)列中, 然后再由消費(fèi)者線程慢慢的來處理每個(gè)支付請(qǐng)求.這樣做可以有效進(jìn)行 “削峰”, 防止服務(wù)器被突然到來的一波請(qǐng)求直接沖垮.
(2) 阻塞隊(duì)列也能使生產(chǎn)者和消費(fèi)者之間 解耦.
比如過年一家人一起包餃子. 一般都是有明確分工, 比如一個(gè)人負(fù)責(zé)搟餃子皮, 其他人負(fù)責(zé)包. 搟餃子皮的人就是 “生產(chǎn)者”, 包餃子的人就是 “消費(fèi)者”.搟餃子皮的人不關(guān)心包餃子的人是誰(能包就行, 無論是手工包, 借助工具, 還是機(jī)器包), 包餃子的人也不關(guān)心搟餃子皮的人是誰(有餃子皮就行, 無論是用搟面杖搟的, 還是拿罐頭瓶搟, 還是直接從超市買的).
2.1阻塞隊(duì)列對(duì)生產(chǎn)者的優(yōu)化
優(yōu)化一:能夠讓多個(gè)服務(wù)器程序之間更充分的解耦合:
如果不使用生產(chǎn)者和消費(fèi)者模型,此時(shí)A和B的耦合性比較強(qiáng),如果A線程出現(xiàn)一些狀況B就會(huì)掛,B線程出現(xiàn)一些狀況A就會(huì)掛,這時(shí)當(dāng)我們引入阻塞隊(duì)列后我們就可以將A,B線程分開,如果A,B線程掛了有阻塞隊(duì)列的存在下,是不會(huì)影響別的線程
優(yōu)化二:能夠?qū)τ谡?qǐng)求進(jìn)行"削峰填谷":
我們可以聯(lián)想到我國(guó)的三峽大壩,三峽大壩就相當(dāng)于阻塞隊(duì)列,當(dāng)我們遇到雨水大的季節(jié),我們就可以關(guān)閉三峽大壩,利用三峽大壩來存水;當(dāng)我們遇到干旱期,我們就可以打開三峽大壩的門,來放水解決干旱問題
三.標(biāo)準(zhǔn)庫(kù)中的阻塞隊(duì)列
3.1Java提供阻塞隊(duì)列實(shí)現(xiàn)的標(biāo)準(zhǔn)類
java官方也提供了阻塞隊(duì)列的標(biāo)準(zhǔn)類,主要有下面幾個(gè):
標(biāo)準(zhǔn)類 | 說明 |
---|---|
ArrayBlockingQueue | 一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列 |
LinkedBlockingQueue | 一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列 |
PriorityBlockingQueue | 一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列 |
DelayQueue | 一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列 |
SynchronousQueue | 一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列 |
LinkedTransferQueue | 一個(gè)由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列 |
LinkedBlockingDeque | 一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列 |
BlockingQueue接口 | 單向阻塞隊(duì)列實(shí)現(xiàn)了該接口 |
BlockingDeque接口 | 雙向阻塞隊(duì)列實(shí)現(xiàn)了該接口 |
3.2Blockingqueue基本使用
在 Java 標(biāo)準(zhǔn)庫(kù)中內(nèi)置了阻塞隊(duì)列. 如果我們需要在一些程序中使用阻塞隊(duì)列, 直接使用標(biāo)準(zhǔn)庫(kù)中的即可.
BlockingQueue 是一個(gè)接口. 真正實(shí)現(xiàn)的類是 LinkedBlockingQueue.
put 方法用于阻塞式的入隊(duì)列, take 用于阻塞式的出隊(duì)列.
BlockingQueue 也有 offer, poll, peek 等方法, 但是這些方法不帶有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>(); // 入隊(duì)列 queue.put("abc"); // 出隊(duì)列. 如果沒有 put 直接 take, 就會(huì)阻塞. String elem = queue.take();
四.阻塞隊(duì)列實(shí)現(xiàn)
4.1阻塞隊(duì)列的代碼實(shí)現(xiàn)
我們通過 “循環(huán)隊(duì)列” 的方式來實(shí)現(xiàn)
使用 synchronized 進(jìn)行加鎖控制.put 插入元素的時(shí)候, 判定如果隊(duì)列滿了, 就進(jìn)行 wait. (注意, 要在循環(huán)中進(jìn)行 wait. 被喚醒時(shí)不一定隊(duì)列就不滿了, 因?yàn)橥瑫r(shí)可能是喚醒了多個(gè)線程).take 取出元素的時(shí)候, 判定如果隊(duì)列為空, 就進(jìn)行 wait. (也是循環(huán) wait)
我們?cè)谠O(shè)計(jì)阻塞隊(duì)列的時(shí)候可以將隊(duì)列聯(lián)想成一個(gè)圓
class BlockingQueue{ //隊(duì)列里存放的個(gè)數(shù) volatile private int size = 0; //隊(duì)列的頭節(jié)點(diǎn) private int head = 0; //隊(duì)列的尾節(jié)點(diǎn) private int prov = 0; //創(chuàng)建一個(gè)數(shù)組,我們來給這個(gè)數(shù)組的容量設(shè)置為100 private int[] array = new int[100]; //創(chuàng)建一個(gè)專業(yè)的鎖對(duì)象 private Object object = new Object(); //實(shí)現(xiàn)阻塞隊(duì)列中的put方法 public void put(int value) throws InterruptedException { synchronized (object) { //當(dāng)數(shù)組已經(jīng)滿了 if (size == array.length) { object.wait(); } else { //我們可以優(yōu)化成prov = (prov + 1) % items.length array[prov] = value; prov ++; if (prov >= array.length) { prov = 0; } } size++; object.notify(); } } //實(shí)現(xiàn)阻塞隊(duì)列中的take方法 public int take() throws InterruptedException { synchronized (object) { if (size == 0) { object.wait(); } int x = array[head]; head++; if (head >= array.length) { head = 0; } size--; object.notify(); return x; } } }
4.2阻塞隊(duì)列搭配生產(chǎn)者與消費(fèi)者的代碼實(shí)現(xiàn)
class BlockingQueue{ //隊(duì)列里存放的個(gè)數(shù) volatile private int size = 0; //隊(duì)列的頭節(jié)點(diǎn) private int head = 0; //隊(duì)列的尾節(jié)點(diǎn) private int prov = 0; //創(chuàng)建一個(gè)數(shù)組,我們來給這個(gè)數(shù)組的容量設(shè)置為100 private int[] array = new int[100]; //創(chuàng)建一個(gè)專業(yè)的鎖對(duì)象 private Object object = new Object(); //實(shí)現(xiàn)阻塞隊(duì)列中的put方法 public void put(int value) throws InterruptedException { synchronized (object) { //當(dāng)數(shù)組已經(jīng)滿了 if (size == array.length) { object.wait(); } else { //我們可以優(yōu)化成prov = (prov + 1) % items.length array[prov] = value; prov ++; if (prov >= array.length) { prov = 0; } } size++; object.notify(); } } //實(shí)現(xiàn)阻塞隊(duì)列中的take方法 public int take() throws InterruptedException { synchronized (object) { if (size == 0) { object.wait(); } int x = array[head]; head++; if (head >= array.length) { head = 0; } size--; object.notify(); return x; } } } public class Test { public static void main(String[] args) { BlockingQueue blockingQueue = new BlockingQueue(); Thread thread1 = new Thread(()-> { while (true) { for (int i = 0; i < 100; i++) { try { blockingQueue.put(i); System.out.println("生產(chǎn)了"+i); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread thread2 = new Thread(()->{ while (true) { try { int b = blockingQueue.take(); System.out.println("消耗了"+b); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread1.start(); thread2.start(); } }
以上就是Java多線程案例之阻塞隊(duì)列詳解的詳細(xì)內(nèi)容,更多關(guān)于Java多線程阻塞隊(duì)列的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Spring中Bean后置處理器(BeanPostProcessor)的使用
BeanPostProcessor 接口也被稱為Bean后置處理器,通過該接口可以自定義調(diào)用初始化前后執(zhí)行的操作方法。本文將詳細(xì)講講它的使用,需要的可以參考一下2022-06-06Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn)
在我們數(shù)據(jù)庫(kù)中有些時(shí)候會(huì)保存一些用戶的敏感信息,所以就需要對(duì)這些數(shù)據(jù)進(jìn)行加密,那么本文就介紹了Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn),感興趣的可以了解一下2021-06-06Java語法基礎(chǔ)之運(yùn)算符學(xué)習(xí)筆記分享
這篇文章主要為大家分享了Java語法基礎(chǔ)之運(yùn)算符學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09詳解Java遞歸實(shí)現(xiàn)樹形結(jié)構(gòu)的兩種方式
在開發(fā)的過程中,很多業(yè)務(wù)場(chǎng)景需要一個(gè)樹形結(jié)構(gòu)的結(jié)果集進(jìn)行前端展示,也可以理解為是一個(gè)無限父子結(jié)構(gòu),常見的有報(bào)表指標(biāo)結(jié)構(gòu)、菜單結(jié)構(gòu)等,這篇文章主要介紹了Java遞歸實(shí)現(xiàn)樹形結(jié)構(gòu)的兩種方式,需要的朋友可以參考下2022-10-10Java 數(shù)據(jù)結(jié)構(gòu)中二叉樹前中后序遍歷非遞歸的具體實(shí)現(xiàn)詳解
樹是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹中稱為結(jié)點(diǎn))按分支關(guān)系組織起來的結(jié)構(gòu),很象自然界中的樹那樣。樹結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會(huì)的族譜和各種社會(huì)組織機(jī)構(gòu)都可用樹形象表示2021-11-11