• 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 ,此时应该使用线程安全的集合。

  • 相关阅读:
    C++读取XML,开进程,开线程
    WinRT 异步模型
    记一个宣告失败的项目Metro VS2012RC的感性认识
    [翻译]深入理解Win32结构化异常处理(四)
    Google Test 测试架构探究
    .NET FrameWork 技术内幕
    [翻译]深入理解Win32结构化异常处理(三)
    谈一谈软件开发
    ExecutionContext & SynchronizationContext
    软件制作:QQPenguin辅助工具
  • 原文地址:https://www.cnblogs.com/caca/p/java_arraylist_vector_threadsafe_iterator_fail-fast.html
Copyright © 2020-2023  润新知