面试题答案
一键面试1. CopyOnWriteArrayList线程安全实现原理
CopyOnWriteArrayList通过“写时复制”机制实现线程安全。当进行写操作(如add、remove等)时,会创建底层数组的一个副本,在副本上进行修改,完成后再将原数组引用指向新的副本。而读操作(如get等)则直接读取原数组,不进行加锁操作。
2. 读写操作具体实现方式
- 写操作:
- 以
add(E e)
方法为例,其实现如下:
- 以
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();
}
}
- 首先获取锁,保证同一时间只有一个线程能进行写操作。然后复制原数组,在新数组末尾添加元素,最后更新数组引用。
- 读操作:
- 以
get(int index)
方法为例:
- 以
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
- 直接从数组中获取元素,没有加锁操作。
3. 对性能和内存的影响
- 性能:
- 读性能:读操作性能高,因为无需加锁,适合读多写少的场景。多个线程可以并发进行读操作,不会发生竞争。
- 写性能:写操作性能相对较低,因为每次写操作都要复制数组,这涉及内存分配和数据复制,开销较大。同时,写操作时会加锁,限制了写操作的并发度。
- 内存:
- 由于写操作需要复制数组,在写操作频繁时,会占用更多的内存空间。如果数组元素较多,每次复制产生的新数组会导致内存使用量显著增加。在写操作完成前,新旧数组同时存在于内存中,可能会引发内存压力甚至内存溢出问题。