溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中并發(fā)容器的示例分析

發(fā)布時(shí)間:2021-06-07 10:11:46 來源:億速云 閱讀:117 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)Java中并發(fā)容器的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

一、并發(fā)容器

1.1 JDK 提供的并發(fā)容器總結(jié)

JDK 提供的這些容器大部分在java.util.concurrent包中。

ConcurrentHashMap: 線程安全的 HashMap

CopyOnWriteArrayList: 線程安全的 List,在讀多寫少的場合性能非常好,遠(yuǎn)遠(yuǎn)好于 Vector.

ConcurrentLinkedQueue: 高效的并發(fā)隊(duì)列,使用鏈表實(shí)現(xiàn)。可以看做一個(gè)線程安全的

LinkedList,這是一個(gè)非阻塞隊(duì)列。

BlockingQueue: 這是一個(gè)接口,JDK 內(nèi)部通過鏈表、數(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 不是線程安全的,在并發(fā)場景下如果要保證一種可行的方式是使用
Collections.synchronizedMap()方法來包裝我們的 HashMap。但這是通過使用一個(gè)全局的鎖來同步不同線程間的并發(fā)訪問,因此會(huì)帶來不可忽視的性能問題。

所以就有了 HashMap 的線程安全版本—— ConcurrentHashMap 的誕生。在 ConcurrentHashMap 中,無論是讀操作還是寫操作都能保證很高的性能:在進(jìn)行讀操作時(shí)(幾乎)不需要加鎖,而在寫操作時(shí) 通過鎖分段技術(shù)只對(duì)所操作的段加鎖而不影響客戶端對(duì)其它段的訪問。

1.3 CopyOnWriteArrayList

1.3.1 CopyOnWriteArrayList 簡介

Java中并發(fā)容器的示例分析

在很多應(yīng)用場景中,讀操作可能會(huì)遠(yuǎn)遠(yuǎn)大于寫操作。由于讀操作根本不會(huì)修改原有的數(shù)據(jù),因此對(duì)于每 次讀取都進(jìn)行加鎖其實(shí)是一種資源浪費(fèi)。我們應(yīng)該允許多個(gè)線程同時(shí)訪問 List 的內(nèi)部數(shù)據(jù),畢竟讀取操作是安全的。

這和我們之前在多線程章節(jié)講過 ReentrantReadWriteLock 讀寫鎖的思想非常類似,也就是讀讀共享、寫寫互斥、讀寫互斥、寫讀互斥。JDK 中提供了 CopyOnWriteArrayList 類比相比于在讀寫鎖的思想又更進(jìn)一步。為了將讀取的性能發(fā)揮到極致, CopyOnWriteArrayList 讀取是完全不用加鎖的, 并且更厲害的是:寫入也不會(huì)阻塞讀取操作。只有寫入和寫入之間需要進(jìn)行同步等待。這樣一來,讀操 作的性能就會(huì)大幅度提升。那它是怎么做的呢?

1.3.2 CopyOnWriteArrayList 是如何做到的?

Java中并發(fā)容器的示例分析

1.3.3 CopyOnWriteArrayList 讀取和寫入源碼簡單分析
1.3.3.1 CopyOnWriteArrayList 讀取操作的實(shí)現(xiàn)

讀取操作沒有任何同步控制和鎖操作,理由就是內(nèi)部數(shù)組 array 不會(huì)發(fā)生修改,只會(huì)被另外一個(gè) array替換,因此可以保證數(shù)據(jù)安全。

Java中并發(fā)容器的示例分析

1.3.3.2 CopyOnWriteArrayList 寫入操作的實(shí)現(xiàn)

CopyOnWriteArrayList 寫入操作 add() 方法在添加集合的時(shí)候加了鎖,保證了同步,避免了多線程寫的時(shí)候會(huì) copy 出多個(gè)副本出來。

Java中并發(fā)容器的示例分析

1.4 ConcurrentLinkedQueue

Java中并發(fā)容器的示例分析

1.5 BlockingQueue

1.5.1 BlockingQueue 簡單介紹

上面我們己經(jīng)提到了 ConcurrentLinkedQueue 作為高性能的非阻塞隊(duì)列。下面我們要講到的是阻塞隊(duì)列——BlockingQueue。阻塞隊(duì)列(BlockingQueue)被廣泛使用在“生產(chǎn)者-消費(fèi)者”問題中,其原因是BlockingQueue 提供了可阻塞的插入和移除的方法。當(dāng)隊(duì)列容器已滿,生產(chǎn)者線程會(huì)被阻塞,直到隊(duì)列未滿;當(dāng)隊(duì)列容器為空時(shí),消費(fèi)者線程會(huì)被阻塞,直至隊(duì)列非空時(shí)為止。

BlockingQueue 是一個(gè)接口,繼承自 Queue,所以其實(shí)現(xiàn)類也可以作為 Queue 的實(shí)現(xiàn)來使用,而Queue 又繼承自 Collection 接口。下面是 BlockingQueue 的相關(guān)實(shí)現(xiàn)類:

Java中并發(fā)容器的示例分析

下面主要介紹一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,這三個(gè) BlockingQueue 的實(shí)現(xiàn)類。

1.5.2 ArrayBlockingQueue

Java中并發(fā)容器的示例分析

 Java中并發(fā)容器的示例分析

1.5.3 LinkedBlockingQueue

LinkedBlockingQueue 底層基于單向鏈表實(shí)現(xiàn)的阻塞隊(duì)列,可以當(dāng)做無界隊(duì)列也可以當(dāng)做有界隊(duì)列來使用,同樣滿足 FIFO 的特性,與 ArrayBlockingQueue 相比起來具有更高的吞吐量,為了防止LinkedBlockingQueue 容量迅速增,損耗大量內(nèi)存。通常在創(chuàng)建 LinkedBlockingQueue 對(duì)象時(shí),會(huì)指定其大小,如果未指定,容量等于 Integer.MAX_VALUE。

相關(guān)構(gòu)造方法:

Java中并發(fā)容器的示例分析

1.5.4 PriorityBlockingQueue

Java中并發(fā)容器的示例分析

1.6 ConcurrentSkipListMap

為了引出 ConcurrentSkipListMap,先帶著大家簡單理解一下跳表。

對(duì)于一個(gè)單鏈表,即使鏈表是有序的,如果我們想要在其中查找某個(gè)數(shù)據(jù),也只能從頭到尾遍歷鏈表, 這樣效率自然就會(huì)很低,跳表就不一樣了。跳表是一種可以用來快速查找的數(shù)據(jù)結(jié)構(gòu),有點(diǎn)類似于平衡樹。它們都可以對(duì)元素進(jìn)行快速地查找。但一個(gè)重要的區(qū)別是:對(duì)平衡樹的插入和刪除往往很可能導(dǎo)致 平衡樹進(jìn)行一次全局的調(diào)整。而對(duì)跳表的插入和刪除只需要對(duì)整個(gè)數(shù)據(jù)結(jié)構(gòu)的局部進(jìn)行操作即可。這樣 帶來的好處是:在高并發(fā)的情況下,你會(huì)需要一個(gè)全局鎖來保證整個(gè)平衡樹的線程安全。而對(duì)于跳表, 你只需要部分鎖即可。這樣,在高并發(fā)環(huán)境下,你就可以擁有更好的性能。而就查詢的性能而言,跳表的時(shí)間復(fù)雜度也是 O(logn) 所以在并發(fā)數(shù)據(jù)結(jié)構(gòu)中,JDK 使用跳表來實(shí)現(xiàn)一個(gè) Map。

跳表的本質(zhì)是同時(shí)維護(hù)了多個(gè)鏈表,并且鏈表是分層的

Java中并發(fā)容器的示例分析

最低層的鏈表維護(hù)了跳表內(nèi)所有的元素,每上面一層鏈表都是下面一層的子集。

跳表內(nèi)的所有鏈表的元素都是排序的。查找時(shí),可以從頂級(jí)鏈表開始找。一旦發(fā)現(xiàn)被查找的元素大于當(dāng)前鏈表中的取值,就會(huì)轉(zhuǎn)入下一層鏈表繼續(xù)找。這也就是說在查找過程中,搜索是跳躍式的。如上圖所示,在跳表中查找元素 18。

Java中并發(fā)容器的示例分析

查找 18 的時(shí)候原來需要遍歷 18 次,現(xiàn)在只需要 7 次即可。針對(duì)鏈表長度比較大的時(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)的類是ConcurrentSkipListMap。

感謝各位的閱讀!關(guān)于“Java中并發(fā)容器的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI