• 目录:

    读 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

    1. 默认容量是10
    2. add 的时候。先做扩容相关的确认,再设置数据
    3. 容量完全使用光的时候才扩容。这点和 Map 的有小于1的负荷系数不一样,Map 的填充是一个概率问题。但是 Array 的填充是明确的。
    4. 新的容量取的是 new = old + (old » 1),也就是原来的 1.5 倍。

    (有个神奇的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,为啥 -8 呢…答案就在源码注释里 →.→)