Java CopyOnWriteArrayList面试题

1. 什么是CopyOnWriteArrayList?

CopyOnWriteArrayList是一种线程安全的变长数组,适用于读多写少的场景。在每次修改操作(添加、删除等)时,都会复制一份数组,因此读操作不需要加锁,提供较高的读性能。

CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();

2. CopyOnWriteArrayList的构造方法有哪些?

CopyOnWriteArrayList提供了三种构造方法:

  • 无参数构造方法,创建一个空的CopyOnWriteArrayList。
  • 接受一个Collection对象作为参数的构造方法,将集合中的元素复制到新创建的CopyOnWriteArrayList中。
  • 接受一个数组作为参数的构造方法,将数组中的元素复制到新创建的CopyOnWriteArrayList中。
CopyOnWriteArrayList<String> cowList1 = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList<String> cowList2 = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
CopyOnWriteArrayList<String> cowList3 = new CopyOnWriteArrayList<>("a", "b", "c");

3. CopyOnWriteArrayList是如何保证线程安全的?

CopyOnWriteArrayList通过在每次修改操作时复制整个底层数组来保证线程安全。读操作直接在旧数组上进行,不受写操作影响,因此不需要加锁。

4. CopyOnWriteArrayList的add方法是如何实现的?

add方法在添加元素时,首先复制一份当前数组,然后在新数组上添加元素,最后将原数组引用指向新数组。

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

5. CopyOnWriteArrayList的读操作是否需要加锁?

不需要,CopyOnWriteArrayList的读操作(如get)不需要加锁,因为它们直接在当前数组上进行,不受写操作影响。

String element = cowList.get(index);

6. CopyOnWriteArrayList的迭代器有什么特点?

CopyOnWriteArrayList的迭代器是弱一致性的,不会抛出ConcurrentModificationException异常。这意味着迭代器可能会错过在迭代过程中对列表的修改。

Iterator<String> iterator = cowList.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
}

7. CopyOnWriteArrayList适用于什么场景?

适用于读操作远多于写操作的场景,如缓存实现。

8. CopyOnWriteArrayList的性能如何?

在写操作频繁的场景下,CopyOnWriteArrayList的性能较差,因为每次写操作都需要复制整个数组。在读操作频繁的场景下,性能较好。

9. CopyOnWriteArrayList是否允许null元素?

是的,CopyOnWriteArrayList允许null元素。

cowList.add(null);

10. CopyOnWriteArrayList的size方法是如何实现的?

size方法返回底层数组的长度,由于读操作不需要加锁,因此返回的值是一个近似值。

int size = cowList.size();

11. CopyOnWriteArrayList的remove方法是如何实现的?

remove方法在删除元素时,同样复制一份当前数组,然后在新数组上删除元素,最后将原数组引用指向新数组。

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

12. CopyOnWriteArrayList是否适用于高并发场景?

适用于读多写少的高并发场景。

13. CopyOnWriteArrayList的迭代器是否是强一致性的?

不是,CopyOnWriteArrayList的迭代器是弱一致性的。

14. CopyOnWriteArrayList的toArray方法有什么作用?

toArray方法返回包含CopyOnWriteArrayList所有元素的数组。

Object[] array = cowList.toArray();

15. CopyOnWriteArrayList的toArray(T[] a)方法有什么作用?

toArray(T[] a)方法返回包含CopyOnWriteArrayList所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

String[] array = cowList.toArray(new String[0]);

16. CopyOnWriteArrayList的元素顺序如何保证?

CopyOnWriteArrayList中的元素顺序是按照它们被插入的顺序来保证的。

17. CopyOnWriteArrayList的get和set方法有什么特点?

get方法直接在当前数组上读取元素,而set方法在修改元素时复制一份当前数组,然后在新数组上修改元素,最后将原数组引用指向新数组。

String element = cowList.get(index);
cowList.set(index, "newElement");

18. CopyOnWriteArrayList为什么并发安全且性能比Vector好?

CopyOnWriteArrayList通过写时复制数组来保证并发安全,读操作不需要加锁,因此在读多写少的场景下性能优于Vector。Vector在每次操作时都需要加锁,导致性能较差。

19. CopyOnWriteArrayList的迭代器为什么不抛出ConcurrentModificationException?

CopyOnWriteArrayList的迭代器是弱一致性的,它允许在迭代过程中对列表进行修改,因此不会抛出ConcurrentModificationException异常。

20. CopyOnWriteArrayList的写操作开销大的原因是什么?

每次写操作(如添加、删除)都需要复制整个数组,这导致写操作的开销较大,尤其是在数组较大时。