日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java 并发 set_高并发下的Java数据结构(List、Set、Map、Queue)

發布時間:2024/7/19 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 并发 set_高并发下的Java数据结构(List、Set、Map、Queue) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.并發List

Vector 或者 CopyOnWriteArrayList 是兩個線程安全的List實現,ArrayList 不是線程安全的。因此,應該盡量避免在多線程環境中使用ArrayList。如果因為某些原因必須使用的,則需要使用Collections.synchronizedList(List list)進行包裝。

示例代碼:

List?list?=?Collections.synchronizedList(new?ArrayList());

...

synchronized?(list)?{

Iterator?i?=?list.iterator();?//?必須在同步塊中

while?(i.hasNext())

foo(i.next());

}

遍歷的操作需要自己加鎖,而add之類的方法則不需要,自己看一下源碼就理解了

CopyOnWriteArrayList 的內部實現與Vector又有所不同。顧名思義,Copy-On-Write 就是 CopyOnWriteArrayList 的實現機制。即當對象進行寫操作時,復制該對象;若進行的讀操作,則直接返回結果,操作過程中不需要進行同步。

CopyOnWriteArrayList 很好地利用了對象的不變性,在沒有對對象進行寫操作前,由于對象未發生改變,因此不需要加鎖。而在試圖改變對象時,總是先獲取對象的一個副本,然后對副本進行修改,最后將副本寫回。

這種實現方式的核心思想是減少鎖競爭,從而提高在高并發時的讀取性能,但是它卻在一定程度上犧牲了寫的性能。

在 get() 操作上,Vector 使用了同步關鍵字,所有的 get() 操作都必須先取得對象鎖才能進行。在高并發的情況下,大量的鎖競爭會拖累系統性能。反觀CopyOnWriteArrayList 的get() 實現,并沒有任何的鎖操作。

在 add() 操作上,CopyOnWriteArrayList 的寫操作性能不如Vector,原因也在于Copy-On-Write。

在讀多寫少的高并發環境中,使用 CopyOnWriteArrayList 可以提高系統的性能,但是,在寫多讀少的場合,CopyOnWriteArrayList ?的性能可能不如 Vector。

Copy-On-Write源碼分析

通過查看CopyOnWriteArrayList類的源碼可知,在add操作上,是使用了Lock鎖做了同步處理,內部拷貝了原數組,并在新數組上進行添加操作,最后將新數組替換掉舊數組。

public?boolean?add(E?e)?{

final?ReentrantLock?lock?=?this.lock;

lock.lock();

try?{

Object[]?elements?=?getArray();

int?len?=?elements.length;

Object[]?newElements?=?Arrays.copyOf(elements,?len?+?1);

newElements[len]?=?e;

setArray(newElements);

return?true;

}?finally?{

lock.unlock();

}

}

CopyOnWriteArrayList的get(int index)方法是沒有任何鎖處理的,直接返回數組對象。

public?E?get(int?index)?{

return?get(getArray(),?index);

}

final?Object[]?getArray()?{

return?array;

}

那么Copy-On-Write的優缺點有哪些呢?

最明顯的就是這是CopyOnWriteArrayList屬于線程安全的,并發的讀是沒有異常的,讀寫操作被分離。缺點就是在寫入時不止加鎖,還使用了Arrays.copyOf()進行了數組復制,性能開銷較大,遇到大對象也會導致內存占用較大。

2.并發Set

和List相似,并發Set也有一個 CopyOnWriteArraySet ,它實現了 Set 接口,并且是線程安全的。它的內部實現完全依賴于 CopyOnWriteArrayList ,因此,它的特性和 CopyOnWriteArrayList 完全一致,適用于 讀多寫少的高并發場合,在需要并發寫的場合,則可以使用Set s = Collections.synchronizedSet(Set s)得到一個線程安全的Set。

示例代碼:

Set?s?=?Collections.synchronizedSet(new?HashSet());

...

synchronized?(s)?{

Iterator?i?=?s.iterator();?//?必須在同步塊中

while?(i.hasNext())

foo(i.next());

}

3.并發Map

在多線程環境下使用Map,一般也可以使用Collections.synchronizedMap()方法得到一個線程安全的 Map(詳見示例代碼1)。但是在高并發的情況下,這個Map的性能表現不是最優的。由于 Map 是使用相當頻繁的一個數據結構,因此 JDK 中便提供了一個專用于高并發的 Map 實現 ConcurrentHashMap。

Collections的示例代碼1:

Map?m?=?Collections.synchronizedMap(new?HashMap());

...

Set?s?=?m.keySet();??//?不需要同步塊

...

synchronized?(m)?{??//?同步在m上,而不是s上!!

Iterator?i?=?s.iterator();?//?必須在同步塊中

while?(i.hasNext())

foo(i.next());

}

1.為什么不能在高并發下使用HashMap?

因為多線程環境下,使用Hashmap進行put操作會引起死循環,導致CPU利用率接近100%,所以在并發情況下不能使用HashMap。

2.為什么不使用線程安全的HashTable?

HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下。因為當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,并且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。

3.ConcurrentHashMap的優勢

ConcurrentHashMap的內部實現進行了鎖分離(或鎖分段),所以它的鎖粒度小于同步的 HashMap;同時,ConcurrentHashMap的 get() 操作也是無鎖的。除非讀到的值是空的才會加鎖重讀,我們知道HashTable容器的get方法是需要加鎖的,那么ConcurrentHashMap的get操作是如何做到不加鎖的呢?原因是它的get方法里將要使用的共享變量都定義成volatile。

鎖分離:首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖。

上述部分文字參考文章:https://www.cnblogs.com/ITtangtang/p/3948786.html

4.并發Queue

在并發隊列上,JDK提供了兩套實現,一個是以 ConcurrentLinkedQueue 為代表的高性能隊列,一個是以 BlockingQueue 接口為代表的阻塞隊列。不論哪種實現,都繼承自 Queue 接口。

ConcurrentLinkedQueue 是一個適用于高并發場景下的隊列。它通過無鎖的方式,實現了高并發狀態下的高性能。通常,ConcurrentLinkedQueue 的性能要好于 BlockingQueue 。

與 ConcurrentLinkedQueue 的使用場景不同,BlockingQueue 的主要功能并不是在于提升高并發時的隊列性能,而在于簡化多線程間的數據共享。

BlockingQueue 典型的使用場景是生產者-消費者模式,生產者總是將產品放入 BlockingQueue 隊列,而消費者從隊列中取出產品消費,從而實現數據共享。

BlockingQueue 提供一種讀寫阻塞等待的機制,即如果消費者速度較快,則 BlockingQueue 則可能被清空,此時消費線程再試圖從 BlockingQueue 讀取數據時就會被阻塞。反之,如果生產線程較快,則 BlockingQueue 可能會被裝滿,此時,生產線程再試圖向 BlockingQueue 隊列裝入數據時,便會被阻塞等待,其工作模式如圖所示。

5.并發Deque

在JDK1.6中,還提供了一種雙端隊列(Double-Ended Queue),簡稱Deque。Deque允許在隊列的頭部或尾部進行出隊和入隊操作。與Queue相比,具有更加復雜的功能。

Deque 接口的實現類:LinkedList、ArrayDeque和LinkedBlockingDeque。

它們都實現了雙端隊列Deque接口。其中LinkedList使用鏈表實現了雙端隊列,ArrayDeque使用數組實現雙端隊列。通常情況下,由于ArrayDeque基于數組實現,擁有高效的隨機訪問性能,因此ArrayDeque具有更好的遍性能。但是當隊列的大小發生變化較大時,ArrayDeque需要重新分配內存,并進行數組復制,在這種環境下,基于鏈表的 LinkedList 沒有內存調整和數組復制的負擔,性能表現會比較好。但無論是LinkedList或是ArrayDeque,它們都不是線程安全的。

LinkedBlockingDeque 是一個線程安全的雙端隊列實現。可以說,它已經是最為復雜的一個隊列實現。在內部實現中,LinkedBlockingDeque 使用鏈表結構。每一個隊列節點都維護了一個前驅節點和一個后驅節點。LinkedBlockingDeque 沒有進行讀寫鎖的分離,因此同一時間只能有一個線程對其進行操作。因此,在高并發應用中,它的性能表現要遠遠低于 LinkedBlockingQueue,更要低于 ConcurrentLinkedQueue 。

總結

以上是生活随笔為你收集整理的java 并发 set_高并发下的Java数据结构(List、Set、Map、Queue)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。