Java并發(fā)容器相關(guān)知識(shí)總結(jié)
一、并發(fā)容器
1.1 JDK 提供的并發(fā)容器總結(jié)
JDK 提供的這些容器大部分在java.util.concurrent包中。
ConcurrentHashMap: 線(xiàn)程安全的 HashMap
CopyOnWriteArrayList: 線(xiàn)程安全的 List,在讀多寫(xiě)少的場(chǎng)合性能非常好,遠(yuǎn)遠(yuǎn)好于 Vector.
ConcurrentLinkedQueue: 高效的并發(fā)隊(duì)列,使用鏈表實(shí)現(xiàn)??梢钥醋鲆粋€(gè)線(xiàn)程安全的
LinkedList,這是一個(gè)非阻塞隊(duì)列。
BlockingQueue: 這是一個(gè)接口,JDK 內(nèi)部通過(guò)鏈表、數(shù)組等方式實(shí)現(xiàn)了這個(gè)接口。表示阻塞隊(duì)列,非常適合用于作為數(shù)據(jù)共享的通道。
ConcurrentSkipListMap: 跳表的實(shí)現(xiàn)。這是一個(gè) Map,使用跳表的數(shù)據(jù)結(jié)構(gòu)進(jìn)行快速查找。
1.2 ConcurrentHashMap
我們知道 HashMap 不是線(xiàn)程安全的,在并發(fā)場(chǎng)景下如果要保證一種可行的方式是使用
Collections.synchronizedMap()方法來(lái)包裝我們的 HashMap。但這是通過(guò)使用一個(gè)全局的鎖來(lái)同步不同線(xiàn)程間的并發(fā)訪(fǎng)問(wèn),因此會(huì)帶來(lái)不可忽視的性能問(wèn)題。
所以就有了 HashMap 的線(xiàn)程安全版本—— ConcurrentHashMap 的誕生。在 ConcurrentHashMap 中,無(wú)論是讀操作還是寫(xiě)操作都能保證很高的性能:在進(jìn)行讀操作時(shí)(幾乎)不需要加鎖,而在寫(xiě)操作時(shí) 通過(guò)鎖分段技術(shù)只對(duì)所操作的段加鎖而不影響客戶(hù)端對(duì)其它段的訪(fǎng)問(wèn)。
1.3 CopyOnWriteArrayList
1.3.1 CopyOnWriteArrayList 簡(jiǎn)介
在很多應(yīng)用場(chǎng)景中,讀操作可能會(huì)遠(yuǎn)遠(yuǎn)大于寫(xiě)操作。由于讀操作根本不會(huì)修改原有的數(shù)據(jù),因此對(duì)于每 次讀取都進(jìn)行加鎖其實(shí)是一種資源浪費(fèi)。我們應(yīng)該允許多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn) List 的內(nèi)部數(shù)據(jù),畢竟讀取操作是安全的。
這和我們之前在多線(xiàn)程章節(jié)講過(guò) ReentrantReadWriteLock 讀寫(xiě)鎖的思想非常類(lèi)似,也就是讀讀共享、寫(xiě)寫(xiě)互斥、讀寫(xiě)互斥、寫(xiě)讀互斥。JDK 中提供了 CopyOnWriteArrayList 類(lèi)比相比于在讀寫(xiě)鎖的思想又更進(jìn)一步。為了將讀取的性能發(fā)揮到極致, CopyOnWriteArrayList 讀取是完全不用加鎖的, 并且更厲害的是:寫(xiě)入也不會(huì)阻塞讀取操作。只有寫(xiě)入和寫(xiě)入之間需要進(jìn)行同步等待。這樣一來(lái),讀操 作的性能就會(huì)大幅度提升。那它是怎么做的呢?
1.3.2 CopyOnWriteArrayList 是如何做到的?
1.3.3 CopyOnWriteArrayList 讀取和寫(xiě)入源碼簡(jiǎn)單分析
1.3.3.1 CopyOnWriteArrayList 讀取操作的實(shí)現(xiàn)
讀取操作沒(méi)有任何同步控制和鎖操作,理由就是內(nèi)部數(shù)組 array 不會(huì)發(fā)生修改,只會(huì)被另外一個(gè) array替換,因此可以保證數(shù)據(jù)安全。
1.3.3.2 CopyOnWriteArrayList 寫(xiě)入操作的實(shí)現(xiàn)
CopyOnWriteArrayList 寫(xiě)入操作 add() 方法在添加集合的時(shí)候加了鎖,保證了同步,避免了多線(xiàn)程寫(xiě)的時(shí)候會(huì) copy 出多個(gè)副本出來(lái)。
1.4 ConcurrentLinkedQueue
1.5 BlockingQueue
1.5.1 BlockingQueue 簡(jiǎn)單介紹
上面我們己經(jīng)提到了 ConcurrentLinkedQueue 作為高性能的非阻塞隊(duì)列。下面我們要講到的是阻塞隊(duì)列——BlockingQueue。阻塞隊(duì)列(BlockingQueue)被廣泛使用在“生產(chǎn)者-消費(fèi)者”問(wèn)題中,其原因是BlockingQueue 提供了可阻塞的插入和移除的方法。當(dāng)隊(duì)列容器已滿(mǎn),生產(chǎn)者線(xiàn)程會(huì)被阻塞,直到隊(duì)列未滿(mǎn);當(dāng)隊(duì)列容器為空時(shí),消費(fèi)者線(xiàn)程會(huì)被阻塞,直至隊(duì)列非空時(shí)為止。
BlockingQueue 是一個(gè)接口,繼承自 Queue,所以其實(shí)現(xiàn)類(lèi)也可以作為 Queue 的實(shí)現(xiàn)來(lái)使用,而Queue 又繼承自 Collection 接口。下面是 BlockingQueue 的相關(guān)實(shí)現(xiàn)類(lèi):
下面主要介紹一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,這三個(gè) BlockingQueue 的實(shí)現(xiàn)類(lèi)。
1.5.2 ArrayBlockingQueue
1.5.3 LinkedBlockingQueue
LinkedBlockingQueue 底層基于單向鏈表實(shí)現(xiàn)的阻塞隊(duì)列,可以當(dāng)做無(wú)界隊(duì)列也可以當(dāng)做有界隊(duì)列來(lái)使用,同樣滿(mǎn)足 FIFO 的特性,與 ArrayBlockingQueue 相比起來(lái)具有更高的吞吐量,為了防止LinkedBlockingQueue 容量迅速增,損耗大量?jī)?nèi)存。通常在創(chuàng)建 LinkedBlockingQueue 對(duì)象時(shí),會(huì)指定其大小,如果未指定,容量等于 Integer.MAX_VALUE。
相關(guān)構(gòu)造方法:
1.5.4 PriorityBlockingQueue
1.6 ConcurrentSkipListMap
為了引出 ConcurrentSkipListMap,先帶著大家簡(jiǎn)單理解一下跳表。
對(duì)于一個(gè)單鏈表,即使鏈表是有序的,如果我們想要在其中查找某個(gè)數(shù)據(jù),也只能從頭到尾遍歷鏈表, 這樣效率自然就會(huì)很低,跳表就不一樣了。跳表是一種可以用來(lái)快速查找的數(shù)據(jù)結(jié)構(gòu),有點(diǎn)類(lèi)似于平衡樹(shù)。它們都可以對(duì)元素進(jìn)行快速地查找。但一個(gè)重要的區(qū)別是:對(duì)平衡樹(shù)的插入和刪除往往很可能導(dǎo)致 平衡樹(shù)進(jìn)行一次全局的調(diào)整。而對(duì)跳表的插入和刪除只需要對(duì)整個(gè)數(shù)據(jù)結(jié)構(gòu)的局部進(jìn)行操作即可。這樣 帶來(lái)的好處是:在高并發(fā)的情況下,你會(huì)需要一個(gè)全局鎖來(lái)保證整個(gè)平衡樹(shù)的線(xiàn)程安全。而對(duì)于跳表, 你只需要部分鎖即可。這樣,在高并發(fā)環(huán)境下,你就可以擁有更好的性能。而就查詢(xún)的性能而言,跳表的時(shí)間復(fù)雜度也是 O(logn) 所以在并發(fā)數(shù)據(jù)結(jié)構(gòu)中,JDK 使用跳表來(lái)實(shí)現(xiàn)一個(gè) Map。
跳表的本質(zhì)是同時(shí)維護(hù)了多個(gè)鏈表,并且鏈表是分層的
最低層的鏈表維護(hù)了跳表內(nèi)所有的元素,每上面一層鏈表都是下面一層的子集。
跳表內(nèi)的所有鏈表的元素都是排序的。查找時(shí),可以從頂級(jí)鏈表開(kāi)始找。一旦發(fā)現(xiàn)被查找的元素大于當(dāng)前鏈表中的取值,就會(huì)轉(zhuǎn)入下一層鏈表繼續(xù)找。這也就是說(shuō)在查找過(guò)程中,搜索是跳躍式的。如上圖所示,在跳表中查找元素 18。
查找 18 的時(shí)候原來(lái)需要遍歷 18 次,現(xiàn)在只需要 7 次即可。針對(duì)鏈表長(zhǎng)度比較大的時(shí)候,構(gòu)建索引查找效率的提升就會(huì)非常明顯。
從上面很容易看出,跳表是一種利用空間換時(shí)間的算法。
使用跳表實(shí)現(xiàn) Map 和使用哈希算法實(shí)現(xiàn) Map 的另外一個(gè)不同之處是:哈希并不會(huì)保存元素的順序,而跳表內(nèi)所有的元素都是排序的。因此在對(duì)跳表進(jìn)行遍歷時(shí),你會(huì)得到一個(gè)有序的結(jié)果。所以,如果你的應(yīng)用需要有序性,那么跳表就是你不二的選擇。JDK 中實(shí)現(xiàn)這一數(shù)據(jù)結(jié)構(gòu)的類(lèi)是ConcurrentSkipListMap。
到此這篇關(guān)于Java并發(fā)容器相關(guān)知識(shí)總結(jié)的文章就介紹到這了,更多相關(guān)Java并發(fā)容器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項(xiàng)目中后端接收前端傳參的方法示例詳解
這篇文章主要介紹了springboot項(xiàng)目中一些后端接收前端傳參的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06詳解java動(dòng)態(tài)代理的2種實(shí)現(xiàn)方式
目前Java開(kāi)發(fā)包中包含了對(duì)動(dòng)態(tài)代理的支持,但是其實(shí)現(xiàn)只支持對(duì)接口的的實(shí)現(xiàn)。這篇文章主要介紹了詳解java動(dòng)態(tài)代理的2種實(shí)現(xiàn)方式 ,有興趣的可以了解一下。2016-11-11springboot如何統(tǒng)一設(shè)置時(shí)區(qū)
這篇文章主要介紹了springboot如何統(tǒng)一設(shè)置時(shí)區(qū)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Java利用序列化實(shí)現(xiàn)對(duì)象深度clone的方法
這篇文章主要介紹了Java利用序列化實(shí)現(xiàn)對(duì)象深度clone的方法,實(shí)例分析了java序列化及對(duì)象克隆的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07java通過(guò)Jsoup爬取網(wǎng)頁(yè)過(guò)程詳解
這篇文章主要介紹了java通過(guò)Jsoup爬取網(wǎng)頁(yè)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09詳解Java無(wú)需解壓直接讀取Zip文件和文件內(nèi)容
本篇文章主要介紹了詳解Java無(wú)需解壓直接讀取Zip文件和文件內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10