一、概述
傳統方式下的Collection在疊代集合時,不允許對集合進行修改。
傳統方式下用Collections工具類提供的synchronizedCollection方法來獲得同步集合。分析該方法的實作源碼:其實就是把方法放到同步代碼塊中,鎖為目前集合對象。
Java5中提供了如下一些同步集合類:
通過檢視java.util.concurrent包下的介紹可以知道有哪些并發集合,如:ConcurrentHashMap 、CopyOnWriteArrayList 、CopyOnWriteArraySet。
二、多線程中的集合問題
場景:有一個集合,對該集合采用疊代器進行周遊之後,又對其添加了一個元素。
我們分别使用Arralist、Vector、Collections.synchronizedList、Collections.synchronizedList使用synchronized關鍵字進行同步控制、使用java.util.concurrent包中的高效的同步集合ConcurrentLinkedQueue進行測試。
首先建立一個任務,該任務周遊集合,又對其添加了一個元素。
public class ModifyCollectionTask implements Runnable {
Collection<Integer> list;
Lock lock = null;
public ModifyCollectionTask(Collection<Integer> slist, Lock lock) {
this.list = slist;
this.lock = lock;
}
public void run() {
//lock.lock();
//try{
// 周遊清單
for (Integer num : list) {
System.out.println("線程"+Thread.currentThread().getName()+"資料為:"+num);
}
// 向清單添加元素
list.add(30);
//}finally{lock.unlock();}
}
}
1. 使用ArraList集合
public class MultiThreadListTest {
public static void main(String[] args) {
Collection<Integer> list = new ArrayList<Integer>();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//啟動100個線程,在多線程環境下,測試集合的同步問題
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
}
結果:
抛出異常:java.util.ConcurrentModificationException
說明:
常用的集合類ArrayList、Map等在多線程操作同一對象時會發生不同步的線程而造成資料讀取和寫入錯誤;通常都是采用synchronized修飾符或Lock将那些方法括起來來確定它們在執行時不會被其他線程打擾。
這樣做雖然解決了資料争用問題,但是在并發性方面付出了更多的代價,因為在疊代期間鎖住整個List會阻塞其他線程,使它們在很長一段時間内不能通路這個清單。
2. 使用Vector集合
public class MultiThreadListTest {
public static void main(String[] args) {
Collection<Integer> list = new Vector<Integer>();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//啟動100個線程,在多線程環境下,測試集合的同步問題
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
}
結果:
抛出異常:java.util.ConcurrentModificationException
說明:
Vector雖然是線程同步的,但僅僅把ArrayList改成Vector還是不對。無論是ArrayList還是Vector,隻要是實作Collection接口的,都要遵循fail-fast(快速失敗)的檢測機制,即在疊代是時候,不能修改集合的元素。一旦發現違法這個規定就會抛出異常。
事實上,Vector相對于ArrayList的線程同步,展現在對集合元素是否髒讀上。即ArrayList允許髒讀,而Vector特殊的機制,不會出現髒讀,但是效率會很差。
3. 使用Collections工具類中的同步包裝方法,将線程不安全ArrayList進行包裝
public class MultiThreadListTest {
public static void main(String[] args) {
Collection<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//啟動100個線程,在多線程環境下,測試集合的同步問題
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
}
結果:
抛出異常:java.util.ConcurrentModificationException
說明:
對于 Collections 的 synchronizedCollection 或者 synchronizedList 方法包裝過的集合來說,對于 iterator() 方法是需要使用者手工進行同步的。
4. 使用java.util.concurrent包中的ConcurrentLinkedQueue高效的同步集合
将ModifyCollectionTask類中的鎖相關代碼注釋加上,恢複到最初。
public class MultiThreadListTest {
public static void main(String[] args) {
Collection<Integer> list = new ConcurrentLinkedQueue<Integer>();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//啟動100個線程,在多線程環境下,測試集合的同步問題
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
}
結果:
沒出現異常。
說明:
對于 java.util.concurrent 中的任何集合都是經過精心設計的,無論疊代、增加、删除都是線程而全的,而且在疊代時不會抛了 ConcurrentModificationException 的異常。
三、參考資料
http://blog.csdn.net/itm_hadf/article/details/7506529
http://long-yu2.iteye.com/blog/1530278
http://blog.csdn.net/johnny901114/article/details/8696032
版權聲明:本文為CSDN部落客「weixin_33795833」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/weixin_33795833/article/details/92568307