在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類中提供的靜態工廠方法創建的類:
Collections類是一個工具提供類,注意,它和Collection不同,Collection是一個頂層的接口。在Collections類中提供了大量的方法,比如對集合或者容器進行排序、查找等操作。最重要的是,在它裡面提供了幾個靜態工廠方法來創建同步容器類。
從同步容器的具體實現源碼可知,同步容器中的方法采用了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異常。
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/