- CopyOnWriteArrayList的subList仅是原始列表的视图,原始列表修改后操作subList会出现ConcurrentModificationException。
- 对CopyOnWriteArrayList的subList任何操作都需要获取读锁,更好的方式是使用不可变对象。
Review代码时发现同事写了一段加载缓存并从缓存中获取部分数据的逻辑,使用CopyOnWriteArrayList实现,代码如下:
1 | static volatile CopyOnWriteArrayList<Info>> FULL_LIST = new CopyOnWriteArrayList<>(); |
更新缓存时对CopyOnWriteArrayList进行操作:1
2
3
4
5void updateCache() {
// some logic
FULL_LIST.add(...);
FULL_LIST.remove(...);
}
这几个方法(add
, remove
, subList
)都会获取ReentrantLock
,看起来似乎没什么问题。但是写一个例子就会发现对subList操作时可能会抛出异常:
1 | public class CWALTest { |
异常信息如下:
1 | Caused by: java.util.ConcurrentModificationException |
查看CopyOnWriteArrayList中subList方法的代码,可以看到subList返回的是一个静态内部类COWSubList对象。
1 | public List<E> subList(int fromIndex, int toIndex) { |
而COWSubList中有一个expectedArray属性,指向是对象创建时CopyOnWriteArrayList的array对象。
1 | private static class COWSubList<E> extends AbstractList<E> implements RandomAccess { |
在之后对子列表操作时,会先调用checkForComodification
方法,若原CopyOnWriteArrayList被修改,则抛出异常。
1 | // only call this holding l's lock |
因此取subList时需要考虑线程安全问题,更好的方式是使用不可变列表(例如Guava的ImmutableList),避免对原始列表的更新导致子列表抛出异常,更新缓存时指向新的ImmutableList对象即可,相关代码如下
1 | static volatile ImmutableList<Info>> FULL_LIST = ImmutableList.of(); |
ImmutableList的subList方法取的的是一个子类SubList,和COWSubList类似,仅是一个视图。但由于列表不可变,仅需要检查索引未越界即可。
1 | class SubList extends ImmutableList<E> { |
此外,不可变列表无需加锁,避免了COWSubList中操作都需要获得锁的不便。测试对于有大量读线程的情况下,ImmutableList读取效率远高于COWSubList。