ArrayList/Vector的原理、线程安全和迭代Fail-Fast

疑问

* ArrayList是非线程非安全的,具体是指什么?具体会产生什么问题?
* ArrayList的内部原理是什么?为什么可以动态扩容?
* Vector是线程安全的,具体是如何实现的?为什么不再推荐使用?还有它的适用场景吗?
* 迭代时集合发生了修改怎么办?什么是fail-fast?

线程安全和非线程安全

Vector内部是如何实现线程安全的?

public class Vector
{
     Object[] elementData;       // 存放元素的数组
     int elementCount;           // 存放元素的实际数量,默认的容量(capacity)是10
     int capacityIncrement;      // 当容量占满时,扩容量,如果未指定,则原先的2倍(doubled)

     // 构造函数
     public Vector(int initialCapacity/* 初始容量 */,int capacityIncrement/*扩容量*/){}
}

 其 capacity()/size()/isEmpty()/indexOf()/lastIndexOf()/removeElement()/addElement() 等方法均是 sychronized 的,所以,对Vector的操作均是线程安全的。

Vector是线程安全的,有问题吗?

如果用户知道自己是在单线程情况下运行,那么Vector本身的线程安全就没有必要了,耗费性能。JDK至少要提供一种非线程安全的List,供用户在不同的场景中选择,由此ArrayList出现了。ArrayList虽然是非线程安全的,但如果你想使用线程安全的ArrayList,可以在ArrayList的基础上,通过同步块来实现,或者使用同步包装器(Collections.synchronizedList),还可以使用J.U.C中的CopyOnWriteArrayList。但对于Vector,在其基础之上没有办法获得非线程安全的Vector(无法解耦)。这说明,在设计Vector时,没有做好分离性(数据结构功能和同步功能的分离)。

ArrayList的非线程安全会有什么问题?

Demo

final ArrayList<String> list = new ArrayList<String>(); // 多线程共享的ArrayList
for(int i=0;i<100;i++) // 多个线程同时进行写操作
{
     new Thread(new Runnable(){
        @Override
        public void run() {
        for(int j=0;j<1000;j++)
        {
             list.add("hello"); // 多线程下,此处引发ArrayIndexOutOfBoundsException
       }
    }}).start();
}

ArrayList的内部原理

public class ArrayList<E>
{
    private Object[] elementData;      // 存储元素的数组。其分配的空间长度是capacity。
    private int size;                  // elementData存储了多少个元素。

    public ArrayList(){this(10);};     // 默认capacity是10

    boolean add(E e)
    {
         ensureCapacityInternal(size + 1);  // capacity至少为 size+1
         elementsData[size++]=e;            // size++
         return true;
    }
    void ensureCapacityInternal(int minCapacity){
         if(minCapacity > elementData.length)     // 扩容
              grow(minCapacity);
    }
    void grow(int minCapacity){
         int oldCapacity = elementData.length;
         int newCapacity = oldCapacity + (oldCapacity >> 1);     // 约是原先的1.5倍。
         elementData = Arrays.copyOf(elementData,newCapacity );
    }
}

如何实现线程安全的ArrayList?

#1:自己手动同步
public static List<E> list = ... ;
lock.lock();
list.add();
lock.unlock();

#2:使用同步包装器
List<E> syncList = Collections.synchronizedList(new ArrayList<E>());
迭代时,需要包含在同步块当中
synchronized(syncList){
    while(Iterator<E> iter = syncList.iterator();iter.hasNext();){}
}

#3:使用J.U.C中的CopyOnWriteArrayList。

迭代 / Fail-fast

什么是fail-fast?

一个fail-fast的系统是指当发现任何可能导致过程失败的情况时,立刻抛出错误。一个fail-fast的迭代器是指当迭代的集合正在迭代时检测到集合发生了修改,就立刻抛出一个异常。

ArrayList的fail-falst的实现

public class ArrayList
{
     protected transient int modCount = 0; // 用来记录修改次数(继承自AbstractList)

     add() / remove() / trimToSize() / ensureCapacity() ...
     {
          modCount++; // 每次修改,modCount均自增
     }

     class Itr implements Iterator<E>
     {
          int expectedModCount = modCount;     // 记录modeCount当前值(一次快照)
          
          public E next() {
               checkForComodification();       // next()操作之前,check一次
               ...
          }

          public void remove(){
               checkForComodification();       // remove()操作之前,check一次
               ...
               ArrayList.this.remove(lastRet);
               ...
         // 更新modCount的快照
         // 这说明通过iter的Remove()来删除元素不会抛出ConcurrentModificationException expectedModCount
= modCount; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }

迭代时如何修改ArrayList?

Iterator<String> iter = list.iterator();
int j=0;
while(iter.hasNext())
{
    System.out.println(iter.next());
    if(j==3)
    {
         list.remove(0);  // 出现 ConcurrentModificationException。
         iter.remove();   // (单线程下)不会引发ConcurrentModificationException。但迭代器也只有这个修改相关的操作。
    }
    j++;
}

ConcurrentModificationException 这个异常看起来像是“多线程并发修改异常”,其实单线程下的迭代时修改也可能会出现这个异常。单线程下,迭代时通过集合自身的操作修改集合,会引发异常;通过迭代器修改(即 iter.remove() )不会引发 ConcurrentModificationException 。多线程下,迭代时通过迭代器修改可能会引发 ConcurrentModificationException ,此时应该使用线程安全的集合。

posted @ 2014-04-19 08:01 cacard 阅读(...) 评论(...) 编辑 收藏