轉:java多線程--同步容器,java多線程--容器
在Java的集合容器架構中,主要有四大類別:List、Set、Queue、Map。List、Set、Queue介面分別繼承了Collection介面,Map本身是一個介面。注意Collection和Map是一個頂層介面,而List、Set、Queue則繼承了Collection介面,分別代表數組、集合和隊列這三大類容器。像ArrayList、LinkedList、HashMap這些容器都是非安全執行緒的。因此,在編寫程式時,必須要求程式員手動地在任何訪問到這些容器的地方進行同步處理,這樣導致在使用這些容器的時候非常地不方便。
1)Vector、Stack、HashTable
Vector實現了List介面,Vector實際上就是一個數組,和ArrayList類似,但是Vector中的方法都是synchronized方法,即進行了同步措施。
Stack也是一個同步容器,它的方法也用synchronized進行了同步,它實際上是繼承於Vector類。
HashTable實現了Map介面,它和HashMap很相似,但是HashTable進行了同步處理,而HashMap沒有。
2)Collections類中提供的靜態Factory 方法建立的類:
Collections類是一個工具提供類,注意,它和Collection不同,Collection是一個頂層的介面。在Collections類中提供了大量的方法,比如對集合或者容器進行排序、尋找等操作。最重要的是,在它裡面提供了幾個靜態Factory 方法來建立同步容器類。
從同步容器的具體實現源碼可知,同步容器中的方法採用了synchronized進行了同步,那麼很顯然,這必然會影響到執行效能,另外,同步容器不一定是真正地完全安全執行緒的,還需要添加額外的同步措施。
例如:
1 public class Test { 2 static Vector<Integer> vector = new Vector<Integer>(); 3 public static void main(String[] args) throws InterruptedException { 4 while(true) { 5 for(int i=0;i<10;i++) 6 vector.add(i); 7 Thread thread1 = new Thread(){ 8 public void run() { 9 for(int i=0;i<vector.size();i++)10 vector.remove(i);11 };12 };13 Thread thread2 = new Thread(){14 public void run() {15 for(int i=0;i<vector.size();i++)16 vector.get(i);17 };18 };19 thread1.start();20 thread2.start();21 while(Thread.activeCount()>10) {22 23 }24 }25 }26 }View Code 1 public class Test { 2 static Vector<Integer> vector = new Vector<Integer>(); 3 public static void main(String[] args) throws InterruptedException { 4 while(true) { 5 for(int i=0;i<10;i++) 6 vector.add(i); 7 Thread thread1 = new Thread(){ 8 public void run() { 9 synchronized (Test.class) { //進行額外的同步10 for(int i=0;i<vector.size();i++)11 vector.remove(i);12 }13 };14 };15 Thread thread2 = new Thread(){16 public void run() {17 synchronized (Test.class) {18 for(int i=0;i<vector.size();i++)19 vector.get(i);20 }21 };22 };23 thread1.start();24 thread2.start();25 while(Thread.activeCount()>10) {26 27 }28 }29 }30 }View Code
- ConcurrentModificationException異常
在對容器並發地進行迭代修改時,會報ConcurrentModificationException異常。
1 public class Test { 2 public static void main(String[] args) { 3 ArrayList<Integer> list = new ArrayList<Integer>(); 4 list.add(2); 5 Iterator<Integer> iterator = list.iterator(); 6 while(iterator.hasNext()){ 7 Integer integer = iterator.next(); 8 if(integer==2) 9 list.remove(integer);10 }11 }12 }
這樣會報ConcurrentModificationException異常,我們定位到源碼查看。
1 private class Itr implements Iterator<E> { 2 int cursor; // index of next element to return 3 int lastRet = -1; // index of last element returned; -1 if no such 4 int expectedModCount = modCount; 5 6 public boolean hasNext() { 7 return cursor != size; 8 } 9 10 @SuppressWarnings("unchecked")11 public E next() {12 checkForComodification();13 int i = cursor;14 if (i >= size)15 throw new NoSuchElementException();16 Object[] elementData = ArrayList.this.elementData;17 if (i >= elementData.length)18 throw new ConcurrentModificationException();19 cursor = i + 1;20 return (E) elementData[lastRet = i];21 }22 23 public void remove() {24 if (lastRet < 0)25 throw new IllegalStateException();26 checkForComodification();27 28 try {29 ArrayList.this.remove(lastRet);30 cursor = lastRet;31 lastRet = -1;32 expectedModCount = modCount;33 } catch (IndexOutOfBoundsException ex) {34 throw new ConcurrentModificationException();35 }36 }37 38 final void checkForComodification() {39 if (modCount != expectedModCount)40 throw new ConcurrentModificationException();41 }42 }View Code
首先我們看一下它的幾個成員變數:
cursor:表示下一個要訪問的元素的索引,從next()方法的具體實現就可看出
lastRet:表示上一個訪問的元素的索引
expectedModCount:表示對ArrayList修改次數的期望值,它的初始值為modCount。
modCount是AbstractList類中的一個成員變數,該值表示對List的修改次數,查看ArrayList的add()和remove()方法就可以發現,每次調用add()方法或者remove()方法等就會對modCount進行加1操作。
因為modCount的修改,而導致expectedModCount和modCount不相等,即checkForComodification()拋異常導致。
解決方案:
1:單線程環境下:iterator.remove();
2:多線程環境下:
1)在使用iterator迭代的時候使用synchronized或者Lock進行同步;
2)使用並發容器CopyOnWriteArrayList代替ArrayList和Vector。
大家都知道HashMap是非安全執行緒的,Hashtable是安全執行緒的,但是由於Hashtable是採用synchronized進行同步,相當於所有線程進行讀寫時都去競爭一把鎖,導致效率非常低下。ConcurrentHashMap可以做到讀取資料不加鎖,並且其內部的結構可以讓其在進行寫操作的時候能夠將鎖的粒度保持地盡量地小,不用對整個ConcurrentHashMap加鎖。
CopyOnWrite容器即寫時複製的容器。通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裡添加元素,添加完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行並發的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
CopyOnWrite容器有很多優點,但是同時也存在兩個問題,即記憶體佔用問題和資料一致性問題:CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的即時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。
參考部落格:
http://home.cnblogs.com/u/dolphin0520/