Java arrayList工作原理详解及并发

一.arrayList 概述

1.Java API 中的官方描述:

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

总结几点:实现了list接口,允许插入任何类型的元素。非线程安全。

2.以数组实现,节约空间,但有容量限制,默认初始化大小为10,当超出后,会自动扩容为原来的1/2(50%)的容量,即自动扩容机制。

源码如下:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//通过这句:得知扩容大小 为1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
3.按数组下标来查找元素,性能很高,这是基本优势,get(index)/set(index,e);

添加add(e)操作性能也很好(直接在数组末尾添加元素),但是按照下标来插入元素或者删除操作,add(index,e)/remove(index)/remove(e),性能会低,需要使用 system.arraycopy()来移动受影响的元素。

二.add  、 get  、 remove

1.add

Java arrayList工作原理详解及并发_第1张图片

添加 元素 相当于 在链表中置位,但是remove元素 相当于把0位置元素移除以后,以后的元素需要向上移动位置。

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
对于 get(index)

先检查index是否越界,然后按索引查询。

remove(index)

public E remove(int index) {
        rangeCheck(index);//检查是否越界

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
//最原始数组结构不变,从当前元素的下一个位置作为数组的当前位置进行复制,复制长度为size-index-1;
//例如,size=10,remove 的index=1,那么复制长度length=8,list a = {1,2,3,4,null,null,null,null,null,null}
//list arraycopy_a = {1,3,4,null,null,null,null,null,null,2}
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
//将最后一个元素置空
        elementData[--size] = null; // Let gc do its work
	//返回remove的元素值
        return oldValue;
    }

三.并发处理

1.无论在直接迭代还是在Java5.0引入的for-each循环语法中,对容器类进行迭代的标准方式都是使用Iterator。然而,如果其他线程并发的修改容器,那么即使是使用迭代器也无法避免在迭代期间对容器加锁,在设计同步容器类的迭代器时并没有考虑到并发修改的问题,并且它们表现出的行为是“及时-失败”fast-failed的,这意味着当容器在迭代过程中被修改时,就会抛出一个concurrentModificationException异常。

这种“及时-失败”的迭代器并不是一种完备的处理机制,而只是捕获并发错误,因此只是并发问题的预警指示器。它们采用的实现方式是,将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么hasNext或next将抛出concurrentModificationException。然而,这种检查是没有同步的情况下进行的,因此可能会看到失效的计数值,而迭代器可能并没有意识到已经发生了修改。这是一种设计上的权衡,从而降低了并发修改操作的检测代码对程序性能带来的影响。(在单线程代码中也可能抛出concurrentModificationException异常,当对象直接从容器中删除而不是通过Iterator.remove 来删除时,就会抛出这个异常)

2.使用并发容器copyOnWriteArrayList会解决并发问题。

但是不建议写多-读少的场景中使用,最适合在 读多-写少 的场景中使用,例如 缓存。

CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。
这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。允许使用所有元素,包括null。

它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。所以在多线程操作时不会出现java.util.ConcurrentModificationException错误。发生修改时候做copy,新老版本分离,保证读的高性能,适用于以读为主的情况。

参考资料:

Java ArrayList工作原理及实现

CopyOnWriteArrayList详解

CopyOnWriteArrayList详解

Java并发实战



你可能感兴趣的:(java)