ArrayList

多条线程并发时,若涉及到对ArrayList的add或remove,即使这些集合看起来并不存在可见性问题,每条线程处理各自的集合,但任然会触发ConcurrentModificationException。

因此回过头来重新认识一下这个最常用的集合,本文主要撸一遍源码及注释,并探究一下抛出异常的原因。

javadoc

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.)

ArrayList是一个实现List接口的可变数组,它实现了列表的所有操作,并允许包括null在内的各种元素。此外,ArrayList还提供了一些方法,可以修改内部,用于存储列表的数组的大小。它大致与Vector相同,只不过不能同步。

The size, isEmpty, get, set, iterator, and listIterator operations run in constant time. The add operation runs in amortized constant time, that is, adding n elements requires O(n) time. All of the other operations run in linear time (roughly speaking). The constant factor is low compared to that for the LinkedList implementation.

ArrayList中,其中size,isEmpty, get, set, iterator, listIterator操作以固定时间constant time执行,add操作是以分摊时间成本的方式执行的,这意味着添加n个元素,就需要O(n)的时间复杂度,而粗略地来说,其他所有操作的时间复杂度都是线性的。 相比LinkedList而言,ArrayList的时间常量constant较低。

Each ArrayList instance has a capacity. The capacity is the size of the array used to store the elements in the list. It is always at least as large as the list size. As elements are added to an ArrayList, its capacity grows automatically. The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost.

每个ArrayList实例的容量是列表内部用于存储元素的数组的大小,集合的容量大于等于列表的长度,向集合内添加元素的时候,容量也会自动增长,但增长策略并不会被指定,因为实际上,这并不止分摊固定时间消耗那么简单。

An application can increase the capacity of an ArrayList instance before adding a large number of elements using the ensureCapacity operation. This may reduce the amount of incremental reallocation.

在向集合添加大量元素之前,可以调用ensureCapacity(int minCapacity)来指增加集合容量,这可以减轻不断增大容量的重分配数量。 扒开源码,可以发现这样的一句注释

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

ArrayList使用一个缓冲数组elementData来实现可变数组的,当然,数组类型自然是Object的了,修饰符为transiant,简而言之,就是不会被序列化。

数组elementData的长度才是集合ArrayList的容量。 第一次add元素的时候,这个数组会被“扩展”到默认容量DEFAULT_CAPACITY,细节如下。

    List<String> list = new ArrayList<>();
        
    list.add("a");

    /*****************************************************************/

    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        //代表初始空的ArrayList
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }


    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }


可见添加元素分为两步

  • 扩容
  • 存到缓冲数组

有意思的是,扩容这一步操作发生在添加第1个元素,添加第11个元素,添加第16个元素,...

        int newCapacity = oldCapacity + (oldCapacity >> 1) 

可以多尝试使用这样的位运算,注意要加括号。

modCount继承自AbstractList,当对集合增删的时候会做出修改,主要被Iterator用来迭代。

Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list: List list = Collections.synchronizedList(new ArrayList(...));

ArrayList并不是同步的。 如果多条线程并发访问,只要有一条线程试图改变ArrayList的列表结构,就必须在外部同步,改变列表机构的意思是指add、delete、clear等明确地改变内部数组大小的操作,只改变某个元素的值并不算在结构上的修改。 如果要修改,通常是同步封装ArrayList的对象来实现的,如果没被封装,那就需要在创建的时候,用Collection.synchronizedList()包装。

    List<String> list = Collections.synchronizedList(new ArrayList<>());

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Collection接口最终继承了Iterable接口,因此ArrayList的iterator()方法将返回一个Iterator接口的实例,细细想想其中多态的感觉,Xxxable的接口规定了能力,Xxxor的接口定义了父态,这样的设计将迭代分了开来。

除了iterator(),还有listIterator()可以获得该list的迭代器,只不过,所获得的iterator是fail-fast的,在这种机制下,迭代过程中只要有除iterator本身的remove()和add(),其他任何改变list结构的操作都会使iterator报异常。 ConcurrentModificationException。 因此,面对并发修改,iterator果断报错(fast fail),而不会让不确定的事情在不确定的时间发生。但实际上,单线程时,iterator同样会报错

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

需要注意的是,iterator的fail-fast并未如它保证的那样机智,一般来说,在未同步的并发修改中,是不可能严格保证fail-fast的,也就是说,有可能没跑出异常,iterators尽力而为,best-effort。因此,不能依赖这个异常纠错,只能用来debug。

转载于:https://my.oschina.net/u/3035165/blog/855917

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值