zoukankan      html  css  js  c++  java
  • 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 ,此时应该使用线程安全的集合。

  • 相关阅读:
    二分练习题4 查找最接近的元素 题解
    二分练习题5 二分法求函数的零点 题解
    二分练习题3 查找小于x的最大元素 题解
    二分练习题2 查找大于等于x的最小元素 题解
    二分练习题1 查找元素 题解
    code forces 1176 D. Recover it!
    code forces 1173 B. Nauuo and Chess
    code forces 1173 C. Nauuo and Cards
    吴恩达深度学习课程笔记-15
    吴恩达深度学习课程笔记-14
  • 原文地址:https://www.cnblogs.com/caca/p/java_arraylist_vector_threadsafe_iterator_fail-fast.html
Copyright © 2011-2022 走看看