fail-fast機制及CopyOnWriteArrayList的原理
目錄
- java基礎解析系列(一)---String、StringBuffer、StringBuilder
- java基礎解析系列(二)---Integer緩存及裝箱拆箱
- java基礎解析系列(三)---HashMap原理
- java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現
- java基礎解析系列(五)---HashMap並發下的問題以及HashTable和CurrentHashMap的區別
- java基礎解析系列(六)---注解原理及使用
- java基礎解析系列(七)---ThreadLocal原理分析
- 這是我的博客目錄,歡迎閱讀
先看一個例子
class Te1 extends Thread
{
private List<Integer> list;
public Te1(List<Integer> list)
{
this.list = list;
}
public void run()
{
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int i = iterator.next();
}
}
}
class Te2 extends Thread
{
private List<Integer> list;
public Te2(List<Integer> list)
{
this.list = list;
}
public void run()
{
for (int i = 0; i < list.size(); i++)
{
list.remove(i);
}
}
}
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList();
for (int i = 0; i <100 ; i++) {
list.add(i);
}
Te1 t1=new Te1(list);
Te2 t2=new Te2(list);
t1.start();
t2.start();
}
}
- 一個線程迭代,一個線程進行刪除,運行時拋出ConcurrentModificationException異常
ConcurrentModificationException
- 中文意思為並發修改異常
736 public Iterator<E> iterator() {
737 return new Itr();
738 }
743 private class Itr implements Iterator<E> {
744 int cursor; // index of next element to return
745 int lastRet = -1; // index of last element returned; -1 if no such
746 int expectedModCount = modCount;
747
748 public boolean hasNext() {
749 return cursor != size;
750 }
751
752 @SuppressWarnings("unchecked")
753 public E next() {
754 checkForComodification();
...
763 }
764
765 public void remove() {
766 if (lastRet < 0)
767 throw new IllegalStateException();
768 checkForComodification();
...
778 }
779
780 final void checkForComodification() {
781 if (modCount != expectedModCount)
782 throw new ConcurrentModificationException();
783 }
784 }
- ArrayList有一個內部類Itr,從源碼可以看到這個類的next和remove方法里面都調用了一個chechForModification方法,而從這個方法(780行)的源碼可以看到,他是通過判斷modCount和expectedModCount是否相等來決定是否拋出並發修改異常
- 同時在這個內部類可以看expectedModCount初始化為modCount(746行),后面並沒有修改
377 public boolean add(E e) {
378 ensureCapacity(size + 1); // Increments modCount!!
...
381 }
178 public void ensureCapacity(int minCapacity) {
179 modCount++;
180 ...
189 }
439 public boolean remove(Object o) {
440 if (o == null) {
441 for (int index = 0; index < size; index++)
442 if (elementData[index] == null) {
443 fastRemove(index);
444 return true;
445 }
446 } else {
447 for (int index = 0; index < size; index++)
448 if (o.equals(elementData[index])) {
449 fastRemove(index);
450 return true;
451 }
452 }
453 return false;
454 }
460 private void fastRemove(int index) {
461 modCount++;
...
467 }
- 從ArrayList的add和remove方法源碼可以看到,這兩個方法都會導致modCount的改變
- 那么可以分析為什么之前的代碼會拋出異常,線程A進行迭代,此時expectedModCount已經確定了,后面並沒有進行修改,而此時線程B同時remove,從前面知道remove會導致modCount改變,此時兩者不同導致拋出異常
fail-fast
A fail-fast system is nothing but immediately report any failure that is likely to lead to failure. When a problem occurs, a fail-fast system fails immediately.
In Java, we can find this behavior with iterators. In case, you have called iterator on a collection object, and another thread tries to modify the collection object, then concurrent modification exception will be thrown. This is called fail-fast.
- 中文譯為快速失敗,這是一種錯誤檢測機制。
- 對上文進行翻譯,當在對一個集合進行迭代的時候,其他線程嘗試去修改這個集合,並發修改異常會被拋出。這就叫做快速失敗。
CopyOnWriteArrayList
- CopyOnWriteArrayList可以解決fail-fast的問題,將ArrayList替換成CopyWriteArrayList進行試驗。
public class Test {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList();
for (int i = 0; i <100 ; i++) {
list.add(i);
}
Te1 t1=new Te1(list);
Te2 t2=new Te2(list);
t1.start();
t2.start();
}
- 結果發現並沒有拋出異常,下面從源碼角度來分析
- CopyOnWriteArrayList的remove方法
469 public E remove(int index) {
470 final ReentrantLock lock = this.lock;
471 lock.lock();
472 try {
473 Object[] elements = getArray();
474 int len = elements.length;
475 E oldValue = get(elements, index);
476 int numMoved = len - index - 1;
477 if (numMoved == 0)
478 setArray(Arrays.copyOf(elements, len - 1));
479 else {
480 Object[] newElements = new Object[len - 1];
481 System.arraycopy(elements, 0, newElements, 0, index);
482 System.arraycopy(elements, index + 1, newElements, index,
483 numMoved);
484 setArray(newElements);
485 }
486 return oldValue;
487 } finally {
488 lock.unlock();
489 }
490 }
99 final void setArray(Object[] a) {
100 array = a;
101 }
- 473行獲取當前的Object數組,480行創建一個新的Object數組,再將舊的數組復制到新的數組上,484行將array指向新的數組
956 public Iterator<E> iterator() {
957 return new COWIterator<E>(getArray(), 0);
958 }
991 private static class COWIterator<E> implements ListIterator<E> {
992
993 private final Object[] snapshot;
994
995 private int cursor;
996
997 private COWIterator(Object[] elements, int initialCursor) {
998 cursor = initialCursor;
999 snapshot = elements;
1000 }
1001
1002 public boolean hasNext() {
1003 return cursor < snapshot.length;
1004 }
1005
1010 @SuppressWarnings("unchecked")
1011 public E next() {
1012 if (! hasNext())
1013 throw new NoSuchElementException();
1014 return (E) snapshot[cursor++];
1015 }
1016
- 999行將snapshot指向當前的array
- 1011行執行next方法返回snapshot中元素,那么在遍歷的過程,如果其他線程執行remove並將array指向了新創建的數組,這個snapshot並沒有更新為新的數組,仍然指向的是remove之前的數組
- 從CopyOnWriteArrayList的迭代器也可以發現沒有fail-fast機制.
CopyOnWriteArrayList分析
- 修改代價大,可以從源碼知道,remove還是add方法,都會進行一次數組的復制,這樣消耗了空間(可能導致gc的頻率提高)也消耗了時間
- 讀寫分離,讀寫不一致,讀的時候讀的是舊的數組,寫的時候寫的是新的數組,所以讀的時候不一定是最新的
- 讀的時候不需要進行加鎖,因為寫的時候是寫在新的數組,讀的數組是舊的數組,並不會改變
- 因此,CopyOnWriteArrayList適合讀多寫少的場景
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。