- 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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。