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的写操作开销大的原因是什么?
每次写操作(如添加、删除)都需要复制整个数组,这导致写操作的开销较大,尤其是在数组较大时。