您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Java中并發(fā)容器的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
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)行快速查找。
我們知道 HashMap 不是線程安全的,在并發(fā)場景下如果要保證一種可行的方式是使用
Collections.synchronizedMap()方法來包裝我們的 HashMap。但這是通過使用一個(gè)全局的鎖來同步不同線程間的并發(fā)訪問,因此會(huì)帶來不可忽視的性能問題。
所以就有了 HashMap 的線程安全版本—— ConcurrentHashMap 的誕生。在 ConcurrentHashMap 中,無論是讀操作還是寫操作都能保證很高的性能:在進(jìn)行讀操作時(shí)(幾乎)不需要加鎖,而在寫操作時(shí) 通過鎖分段技術(shù)只對(duì)所操作的段加鎖而不影響客戶端對(duì)其它段的訪問。
在很多應(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ì)大幅度提升。那它是怎么做的呢?
讀取操作沒有任何同步控制和鎖操作,理由就是內(nèi)部數(shù)組 array 不會(huì)發(fā)生修改,只會(huì)被另外一個(gè) array替換,因此可以保證數(shù)據(jù)安全。
CopyOnWriteArrayList 寫入操作 add() 方法在添加集合的時(shí)候加了鎖,保證了同步,避免了多線程寫的時(shí)候會(huì) copy 出多個(gè)副本出來。
上面我們己經(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)類:
下面主要介紹一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,這三個(gè) BlockingQueue 的實(shí)現(xiàn)類。
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)造方法:
為了引出 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è)鏈表,并且鏈表是分層的
最低層的鏈表維護(hù)了跳表內(nèi)所有的元素,每上面一層鏈表都是下面一層的子集。
跳表內(nèi)的所有鏈表的元素都是排序的。查找時(shí),可以從頂級(jí)鏈表開始找。一旦發(fā)現(xiàn)被查找的元素大于當(dāng)前鏈表中的取值,就會(huì)轉(zhuǎn)入下一層鏈表繼續(xù)找。這也就是說在查找過程中,搜索是跳躍式的。如上圖所示,在跳表中查找元素 18。
查找 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ò),可以把它分享出去讓更多的人看到吧!
免責(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)容。