读 CopyOnWriteArrayList
随机挑一个java类来读,今天读的是 JUC 下的 CopyOnWriteArrayList。
线程安全 与 CopyOnWrite
CopyOnWrite 是保证线程安全的一个常用手段,这让我联想到刚接触 guava 的时候,大多数被传说是线程安全的类,在修改的时候都是返回了一个新的对象。
比如 Splitter:
// 使用
Splitter splitter = Splitter.on("|").omitEmptyStrings();
// omitEmptyStrings() 源码
public Splitter omitEmptyStrings() {
return new Splitter(strategy, true, trimmer, limit); // 第二个参数对应 omitEmptyStrings = true
}
在设置是否忽略空白字符串的时候,返回了新的对象。使用的是不同的对象,自然就不存在线程的安全的问题。
主要作用于 iterator
CopyOnWriteArrayList 中的 CopyOnWrite,主要作用发挥在遍历的操作。让遍历操作是基于快照的方式进行,不会有遍历时修改数据的问题,不需要做线程同步的操作。
// CopyOnWriteArrayList.iterator() 方法
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0); // 是一个自定义的特殊 Iterator: COWIterator
}
// COWIterator 构造方法
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements; // 取得快照
}
iterator 对象拿到的只是快照,所以这必须是个特殊的 iterator。iterator.remove()、iterator.set(E e)、iterator.add(E e) 都是 UnsupportedOperation。因为修改的操作没办法影响到当前实际列表。
要完整的线程安全,还是需要加锁的
CopyOnWrite 对 List 的 add() 之类的操作其实是不友好的。每次都创建新的,代价是很大的。
而且因为 Copy、 Write、Replace 的操作都不是原子操作,CopyOnWrite 本身是不可能保证 add() 的线程安全的。所有的修改操作,都还是要加锁。CopyOnWriteArrayList 有个 ReentrantLock 成员变量,所有的写操作都加锁了。
synchronizedList
到目前为止,是否选用这个类,判断依据就很明显了。重遍历 的场景下适合用。
如果是 重修改 的场景该用哪个类呢?怎么找?回到 ArrayList 类里面,这种注意事项级别的问题,通常 jdk 的开发者都会把答案写在基础类的注释里的。
看下注释就得到了 Collections.synchronizedList
这个答案,并且也有 synchronizedList 在遍历方面存在各种问题的描述。
ArrayList
顺带看下 ArrayList
- 默认容量是10
- add 的时候。先做扩容相关的确认,再设置数据
- 容量完全使用光的时候才扩容。这点和 Map 的有小于1的负荷系数不一样,Map 的填充是一个概率问题。但是 Array 的填充是明确的。
- 新的容量取的是 new = old + (old » 1),也就是原来的 1.5 倍。
(有个神奇的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,为啥 -8 呢…答案就在源码注释里 →.→)