面试题答案
一键面试适用并发场景
- 读多写少场景:CopyOnWriteArrayList 非常适合读操作远多于写操作的并发场景。例如,在一些配置信息加载、日志记录等场景中,数据一旦初始化后很少发生变化,但会被大量线程频繁读取。
线程安全实现
- 写操作实现:
- 当执行写操作(如
add
、remove
等方法)时,CopyOnWriteArrayList 会先创建一个原数组的副本。例如,add
方法实现如下:
public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } }
- 这里使用
synchronized
关键字进行同步,保证同一时间只有一个线程能进行写操作。然后创建原数组的副本,在副本上进行写操作,最后将原数组引用指向新的副本数组。
- 当执行写操作(如
- 读操作实现:
- 读操作(如
get
方法)则直接读取当前数组,不需要加锁。例如get
方法实现如下:
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
- 因为读操作不涉及修改数组内容,所以不需要同步,多个线程可以并发读取。
- 读操作(如
对性能的影响
- 写操作性能影响:写操作由于需要创建数组副本,开销较大。每次写操作都要进行数组复制,这涉及内存分配和数据复制,在写操作频繁的场景下,性能会受到明显影响。
- 读操作性能影响:读操作性能较高,因为无需加锁,避免了锁竞争带来的开销,特别适合高并发读的场景。但是,由于读的是一个快照数据,如果写操作频繁,读的数据可能不是最新的,但在允许一定数据延迟的场景下可以接受。