Java并发编程之堵塞队列介绍以及SkipList(跳表)
堵塞隊(duì)列
先了解一下生產(chǎn)者消費(fèi)者模式:
生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的一方,消費(fèi)者就是消費(fèi)數(shù)據(jù)的另一方。在多線程開發(fā)中,如果生產(chǎn)者處理速度很快,而消費(fèi)者處理速度很慢,那么生產(chǎn)者就必須等待消費(fèi)者處理完,才能繼續(xù)生產(chǎn)數(shù)據(jù)。如果消費(fèi)者的處理能力大于生產(chǎn)者,那么消費(fèi)者就必須等待生產(chǎn)者。為了解決這種生產(chǎn)消費(fèi)能力不均衡的問題,便有了生產(chǎn)者和消費(fèi)者模式。
生產(chǎn)者和消費(fèi)者模式是通過一個(gè)容器來解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問題,即有了MQ(Message Queue)中間件。生產(chǎn)者和消費(fèi)者彼此之間不直接通信,而是通過阻塞隊(duì)列來進(jìn)行通信,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接推送給阻塞隊(duì)列,消費(fèi)者直接從阻塞隊(duì)列里獲取數(shù)據(jù)。
基本概念:
1)、當(dāng)隊(duì)列滿的時(shí)候,插入元素的線程被阻塞,直達(dá)隊(duì)列不滿。
2)、隊(duì)列為空的時(shí)候,獲取元素的線程被阻塞,直到隊(duì)列不空。
這種模式最常見的就是在MQ里面,即消息隊(duì)列(Message Queue)框架,這里就不多談了。
BlockQueue<T>
堵塞隊(duì)列都是基于這個(gè)接口實(shí)現(xiàn)的
基本接口定義,個(gè)人覺得如果用堵塞隊(duì)列的話就應(yīng)該推薦使用堵塞方法,即put()以及take()
| 方法 | 拋出異常 | 帶有返回值 | 堵塞 | 超時(shí)退出 |
| 插入方法 | add | offer | put | offer(Time) |
| 刪除方法 | remove | poll | take | poll(Time) |
| 判斷是否存在 | element | peek | N/A | N/A |
1)、拋出異常:當(dāng)隊(duì)列滿時(shí),如果再往隊(duì)列里插入元素,會(huì)拋出IllegalStateException異常。當(dāng)隊(duì)列空時(shí),從隊(duì)列里獲取元素會(huì)拋出NoSuchElementException異常。
2)、返回特殊值:當(dāng)往隊(duì)列插入元素時(shí),會(huì)返回元素是否插入成功,成功返回true。如果是移除方法,則是從隊(duì)列里取出一個(gè)元素,如果沒有則返回null。
3)、一直阻塞:當(dāng)阻塞隊(duì)列滿時(shí),如果生產(chǎn)者線程往隊(duì)列里put元素,隊(duì)列會(huì)一直阻塞生產(chǎn)者線程,直到隊(duì)列可用或者響應(yīng)中斷退出。當(dāng)隊(duì)列空時(shí),如果消費(fèi)者線程從隊(duì)列里take元素,隊(duì)列會(huì)阻塞住消費(fèi)者線程,直到隊(duì)列不為空。
4)、超時(shí)退出:當(dāng)阻塞隊(duì)列滿時(shí),如果生產(chǎn)者線程往隊(duì)列里插入元素,隊(duì)列會(huì)阻塞生產(chǎn)者線程一段時(shí)間,如果超過了指定的時(shí)間,生產(chǎn)者線程就會(huì)退出。
堵塞隊(duì)列分有界和無界隊(duì)列
有界:會(huì)有設(shè)定的隊(duì)列大小,不能無限制的創(chuàng)建隊(duì)列大小
無界:指理論上可以無限隊(duì)列的大小,但是實(shí)際情況,每個(gè)服務(wù)器都會(huì)有大小,只能說是理論上的,實(shí)際應(yīng)用的時(shí)候會(huì)容易撐滿磁盤或內(nèi)存,建議使用有界堵塞隊(duì)列
常用的堵塞隊(duì)列有一下幾種:
ArrayBlockingQueue:一個(gè)由數(shù)組組成的有界堵塞隊(duì)列,是一個(gè)FIFO隊(duì)列,不過要求創(chuàng)建對(duì)象的時(shí)候創(chuàng)建大小,內(nèi)部是由ReentrantLock和Condition實(shí)現(xiàn)堵塞策略,單個(gè)鎖
/** Main lock guarding all access */final ReentrantLock lock;/** Condition for waiting takes */private final Condition notEmpty;/** Condition for waiting puts */private final Condition notFull;LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界堵塞隊(duì)列,也是一個(gè)FIFO隊(duì)列,不要求創(chuàng)建對(duì)象是設(shè)置默認(rèn)大小,不設(shè)置為Integer.MAX_VAUE;有兩個(gè)鎖,一個(gè)用于插入元素,另外一個(gè)用于獲取元素
/** Lock held by take, poll, etc */private final ReentrantLock takeLock = new ReentrantLock();/** Wait queue for waiting takes */private final Condition notEmpty = takeLock.newCondition();/** Lock held by put, offer, etc */private final ReentrantLock putLock = new ReentrantLock();/** Wait queue for waiting puts */private final Condition notFull = putLock.newCondition();PriorityBlockingQueue:一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。默認(rèn)情況下,按照自然順序,要么實(shí)現(xiàn)compareTo()方法,指定構(gòu)造參數(shù)Comparator。
DelayQueue:一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列。支持延時(shí)獲取的元素的阻塞隊(duì)列,即可以延時(shí)推送,元素必須要實(shí)現(xiàn)Delayed接口。
SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,所以在執(zhí)行速度上會(huì)比其他堵塞隊(duì)列要快,每一個(gè)put操作都要等待一個(gè)take操作;
LinkedBlockingDeque:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。可以從隊(duì)列的頭和尾都可以插入和移除元素,實(shí)現(xiàn)工作密取,方法名帶了First對(duì)頭部操作,帶了last從尾部操作。
跳表(SkipList)
是一種加快查詢鏈表速度的一種方法,以空間換時(shí)間的一種方法,代表的類有ConcurrentSkipListMap和ConcurrentSkipListSet,也是一種隨機(jī)概率數(shù)據(jù)結(jié)構(gòu);
在原有的鏈表結(jié)構(gòu)(鏈表是按順序排序的)上加上一層鏈表結(jié)構(gòu),但這個(gè)是隨機(jī)指定的,類似于數(shù)據(jù)庫的索引,可加多層索引鏈表查詢的時(shí)間復(fù)雜度為O(logn),快跟上了紅黑樹的查詢速度,下面為圖解示例:
** Head nodes Index nodes* +-+ right +-+ +-+* |2|---------------->| |--------------------->| |->null* +-+ +-+ +-+* | down | |* v v v* +-+ +-+ +-+ +-+ +-+ +-+* |1|----------->| |->| |------>| |----------->| |------>| |->null* +-+ +-+ +-+ +-+ +-+ +-+* v | | | | |* Nodes next v v v v v* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+* | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+*寫時(shí)復(fù)制容器
通俗的理解是當(dāng)我們往一個(gè)容器添加元素的時(shí)候,不直接往當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個(gè)新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對(duì)容器進(jìn)行并發(fā)的讀,而不需要加鎖,因?yàn)楫?dāng)前容器不會(huì)添加任何元素。所以寫時(shí)復(fù)制容器也是一種讀寫分離的思想,讀和寫不同的容器。如果讀的時(shí)候有多個(gè)線程正在向容器添加數(shù)據(jù),讀還是會(huì)讀到舊的數(shù)據(jù),因?yàn)閷懙臅r(shí)候不會(huì)鎖住舊的,只能保證最終一致性。
常見的有CopyOnWriteArrayList以及CopyOnWriteArraySet;
適用讀多寫少的并發(fā)場(chǎng)景,常見應(yīng)用:白名單/黑名單, 商品類目的訪問和更新場(chǎng)景。
以下為圖解,在寫完后,原本的引用會(huì)重新指向新的數(shù)組對(duì)象,所以就會(huì)存在內(nèi)存占用問題。
總結(jié)
以上是生活随笔為你收集整理的Java并发编程之堵塞队列介绍以及SkipList(跳表)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: template多行编写的方式
- 下一篇: Java并发编程之synchronize