面试题答案
一键面试实现思路
- 选择继承接口:选择
List
接口,意味着需要实现List
接口定义的所有方法,如add
、get
、remove
等。 - 保证线程安全:
- 使用同步关键字:在关键方法(如
add
、remove
、get
)上使用synchronized
关键字,确保同一时间只有一个线程可以访问这些方法,从而保证线程安全。例如:
- 使用同步关键字:在关键方法(如
public synchronized boolean add(E e) {
// 实际添加元素的逻辑
}
- 使用
java.util.concurrent
包中的工具:如CopyOnWriteArrayList
,它内部实现通过在修改操作(如add
、remove
)时复制整个底层数组,读操作(如get
)直接读取原数组,这样读操作不需要加锁,从而提高性能。
性能优化策略
- 读多写少场景:
- 采用
CopyOnWriteArrayList
类似策略:读操作不涉及锁竞争,提高并发读性能。但写操作时由于需要复制数组,开销较大,所以适用于读多写少场景。
- 采用
- 写多读少场景:
- 减少锁粒度:如果使用
synchronized
关键字,可以考虑使用更细粒度的锁,比如分段锁。例如,将集合按照一定规则(如元素数量)分成多个段,不同段使用不同的锁,这样不同线程可以同时操作不同段的数据,提高并发写性能。
- 减少锁粒度:如果使用
- 通用优化:
- 避免不必要的同步:对于一些不涉及共享数据修改的方法,如获取集合大小
size
方法,可以不进行同步,提高性能。
- 避免不必要的同步:对于一些不涉及共享数据修改的方法,如获取集合大小
关键源码点
synchronized
关键字实现:- 当一个线程进入
synchronized
修饰的方法时,会获取对象的监视器(monitor),其他线程尝试进入该方法时会被阻塞,直到该线程释放监视器。
- 当一个线程进入
CopyOnWriteArrayList
源码: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();
}
}
get
方法:
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
可以看到add
方法加锁并复制数组,get
方法直接读取数组,从而实现读操作无锁。