集合同步
詞匯解析
詞匯解析
如何以線程安全的方式使用Java List?
A.?經典做法
在迭代開始之前,使用synchronized手動同步所有針對List的修改操作(增加、刪除),保證迭代期間沒有線程修改List。此方法需要明確各個方法的同步,考慮的因素很多,不建議使用。
B.?Synchronized Wrappers(包裝器)
使用java.util.Collections的工廠方法Collections.synchronizedXXX(collection),
其保護代理(protectionproxy)模式,僅在正確的條件下才會調用原始List;需要明確指出的是,必須保證使用迭代器訪問List的時候要在同步代碼塊(synchronized block)中,否則有可能拋出ConcurrentModificationException異常,如下(3)例子中所示用法。
C.?Concurrent Collections(并發集合)
1.?copy-on-writecollections
使用CopyOnWriteArrayList,它是完全的線程安全的集合類,包括其迭代器(Iterator & listIterator)也都是完全的線程安全,因此迭代(iteration)期間,不需要同步代碼塊,也不會拋出任何ConcurrentModificationException異常。
特別注意:不要通過迭代器iterator自己的方法(比如add(), set(), remove())去修改集合CopyOnWriteArrayList中的元素,這會引起拋出UnsupportedOperationException異常。
2.?Compare-And-Swap(CAS )collections
ConcurrentLinkedQueue、ConcurrentSkipListMap
3.?using a special lock object collections
LinkedBlockingQueue
1. ArrayList 使用了Collections.synchronizedList(List<T> list)就一定是線程安全了嗎?
答案:不是
2. ArrayList 返回的迭代器iterator不是同步(synchronized)的,因此不是線程安全的,如何解決?
答案:由于迭代器快速失效(fail-fast)的特性,必須保證迭代期間在同步代碼塊內部,避免拋出ConcurrentModificationException異常。
3. 性能對比
Concurrent Collections >?CopyOnWriteArrayList> Synchronized Wrappers >?synchronized
4. 如果想使用雙向鏈表結構的List應該選擇哪種集合類?
答案:首先不能選擇包裝器(Synchronized Wrappers)因為,包裝器不能包裝LinkedList雙向鏈表結構的List,如果強轉會拋出ClassCastException異常,因此只能選擇ConcurrentLinkedQueue或者LinkedBlockingQueue等并發包中的集合類。
5.?LinkedList
@See?http://sudotutorials.com/tutorials/java/collections/java-linkedlist-class.html
6. 包裝器實現方式
注:同步列表上的單個操作保證是原子操作,但如果要以一致的方式執行并發操作(multiple operations),則必須同步(synchronized)該操作。
public class CollectionsSynchronizedList {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList( new LinkedList<>(Arrays.asList("a", "b", "c")));
System.out.println("thread-safe list: " + list);
// single operations are atomic:
list.add("d");
// While Iterating over synchronized list, you must synchronize
// on it to avoid non-deterministic behavior
// multiple operations have to be synchronized:
synchronized (list) {
// Must be in synchronized block
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
System.out.println("item: " + it.next());
}
}
}
}
7. 多線程操作List
publicclassArrayListSynchronization {
publicstaticvoidmain(String[] args) {
finalList<Integer> arrayList =?new?ArrayList<Integer>();
finalList<Integer> synchronizedArrayList;
// Let's make arrayList synchronized
synchronizedArrayList = Collections.synchronizedList(arrayList);
//Thread 1 will add elements in synchronizedArrayList
Thread t1 =?new?Thread(new?Runnable() {
publicvoidrun() {
for(int?i = 0; i <= 3; i++) {
synchronizedArrayList.add(i);
try{
Thread.sleep(100);
}?catch?(InterruptedException e) {
e.printStackTrace();
}
}
}
}, "thread-1");
t1.start();
//Thread 2 will iterate on synchronizedArrayList
Thread t2 =?new?Thread(new?Runnable() {
publicvoidrun() {
Iterator<Integer> it = synchronizedArrayList.iterator();
synchronized(synchronizedArrayList) {?//synchronization block
while(it.hasNext()) {
try{
Thread.sleep(100);
}?catch?(InterruptedException e) {
e.printStackTrace();
}
System.out.println(it.next());
}
}
}
}, "thread-2");
t2.start();
}
}
1. 主線程啟動thread-1,thread-1往synchronizedArrayList里添加0,(也就是 i=0),接著thread-1進入休眠sleep(100),與此同時。
2. 主線程啟動thread-2,thread-2得到synchronizedArrayList對象的鎖,接著thread-2得到synchronizedArrayList的迭代器,然后thread-2進入while循環,接著thread-2進入sleep(100)。
3. 休眠時間結束后thread-1進入運行狀態去執行synchronizedArrayList的add(i)方法,但由于synchronizedArrayList已被synchronized限制(你必須清楚的是只有返回的迭代器是非線程安全外,synchronizedArrayList其余的所有方法都是完全的線程安全的), 因此thread-1必須等待thread-2釋放synchronizedArrayList對象的鎖。
4. 一旦thread-2釋放synchronizedArrayList對象的鎖thread-1將 1 (也就是 i=1) 添加進synchronizedArrayList, 并進一步完成循環,這不會拋出ConcurrentModificationException異常。
原文鏈接:http://www.javamadesoeasy.com/2015/12/how-to-synchronize-arraylist-in-java-to.html
幾乎所有的集合非線程安全的?
ArrayList, LinkedList, HashMap, HashSet, TreeMap, TreeSet, 等都不是同步的,事實上,java.util包內所有的集合中除了Vector 和Hashtable都是非線程安全的。為什么?-因為同步的代價太昂貴了,為了在單線程的應用中取得最大性能,幾乎所有的集合都是非線程安全的。
Vector 和Hashtable 在Java歷史早期就已經存在的,一開始它們就被設計為線程安全的,查看它們的源碼時你會發現其方法都是同步方法。然而它們很快就暴露出多線程下性能非常糟糕的表現。眾所周知,同步(synchronization)需要取得耗費時間監視的同步鎖(locks),因而降低性能。
這就是為什么新的集合(List,Set,Map等)根本不提供并發控制以在單線程應用程序中提供最高性能的原因。
我們在測試兩個類似集合Vector(線程安全) 和 ArrayList(非線程安全)得出以下結論: 在添加同當量的數據時,ArrayList花費的時間明顯要比Vector要少。
多線程與集合類
為了數據安全,多線程環境中,對同一個集合類進行增刪改操作時,如何選擇方案?
1. 選擇線程安全的集合類,比如Vector和Hashtable,不用附加同步代碼。
2. 選擇非線程安全的集合類,必須使用synchronized做同步處理。
3. 選擇非線程安全的集合類,配合同步包裝方法對集合進行包裝,包裝后的對象(List,Set,Map等)是線程安全的,如下所示例子。
List<String> list = Collections.synchronizedList(new LinkedList<>());
Map<String, Account> map = Collections.synchronizedMap(new HashMap<>());
集合類與同步包裝器
java.util.Collections具有為不同類型的Java集合創建同步包裝的方法:
1. <T> Collection<T> synchronizedCollection(Collection<T> c)
Returns a synchronized (thread-safe) collection backed by the specified collection.
2. <T> List<T> synchronizedList(List<T> list)
Returns a synchronized (thread-safe) list backed by the specified list.
3. <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
Returns a synchronized (thread-safe) map backed by the specified map.
4. <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m)
(since Java 1.8)
Returns a synchronized (thread-safe) navigable map backed by the specified navigable map.
5. <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s)
(since Java 1.8)
Returns a synchronized (thread-safe) navigable set backed by the specified navigable set.
6. <T> Set<T> synchronizedSet(Set<T> s)
Returns a synchronized (thread-safe) set backed by the specified set.
7. <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
Returns a synchronized (thread-safe) sorted map backed by the specified sorted map.
8. <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
Returns a synchronized (thread-safe) sorted set backed by the specified sorted set.
拓展閱讀
http://www.codejava.net/java-core/collections/understanding-collections-and-thread-safety-in-java
https://www.geeksforgeeks.org/synchronization-arraylist-java/
總結
- 上一篇: 互联网广告系统综述一生态圈
- 下一篇: 无锁数据结构一