Java 并发容器(一):写时复制的 List 和 Set

  1. CopyOnWrite 什么意思?
    答:写时复制,或称写时拷贝,是解决并发问题的一种重要思路

  2. CopyOnWriteArrayList 的特点是?
    答:CopyOnWriteArrayList 实现了 List 接口,用法与其他 List (如 ArrayList)基本一样,特点如下:

    • CopyOnWriteArrayList 是线程安全的,可以被多个线程并发访问
    • CopyOnWriteArrayList迭代器不支持修改操作,但也不会抛出 ConcurrentModificationException 异常。
    • CopyOnWriteArrayList原子方式支持一些复合操作。
  3. 基于 synchronized 的同步容器与 CopyOnWriteArrayList 的对比?
    答:

    • 迭代时,基于 synchronized 的同步容器需要对整个列表对象加锁,否则会抛出 ConcurrentModification 异常;CopyOnWriteArrayList 没有这个问题,迭代时不需要加锁
    • 复合操作时,比如先检查再更新,基于 synchronized 的同步容器需要调用方加锁;而 CopyOnWriteArrayList 直接支持两个原子方法:public boolean addIfAbsent(E e)/public int addAllAbsent(Collection<? extends E> c)
    • CopyOnWriteArrayList 中,读不需要锁,可以并行;读和写也可以并行;但多个线程不能同时写,每个写操作都需要先获取锁
    • CopyOnWriteArrayList 不适用于数组很大且修改频繁的场景。它是以优化读操作为目标的,读不需要同步,性能很高,但在优化读的同时牺牲了写的性能
  4. 写时复制到底是什么意思?
    答:

    • CopyOnWriteArrayList 的内部也是一个数组,但这个数组是以原子方式被整体更新的每次修改操作,都会新建一个数组,复制原数组的内容到新数组,在新数组上进行需要的修改,然后以原子方式设置内部的数组引用,这就是写时复制。
    • 所有的读操作,都是先拿到当前引用的数组,然后直接访问该数组。在读的过程中,可能内部的数组引用已经被修改了,但不会影响读操作,它依旧访问原数组内容。
    • 换句话说,数组内容是只读的,写操作都是通过新建数组,然后原子性地修改数组引用来实现的
    • 写时复制是一种重要的思维,用于各种计算机程序中,比如操作系统内部的进程管理和内存管理。在进程管理中,子进程经常共享父进程的资源,只有在写时才复制;在内存管理中,当多个程序同时访问同一个文件时,操作系统在内存中可能只会加载一份,只有程序要写时才会复制,分配自己的内存,复制可能也不会全部复制,只会复制写的位置所在的页(页是操作系统管理内存的一个单位,具体大小与系统有关,典型大小为 4 KB)。
  5. CopyOnWriteArrayList 的内部组成大概是?
    答:

    • 内部有一个数组,声明为:private volatile transient Object[] array;。注意有 volatile 修饰,这是必需的,以保证内存可见性,即保证在写操作更改之后读操作能看到
    • 内部使用 ReentrantLock,成员声明为:transient final ReentrantLock lock = new ReentrantLock();
    • 默认构造方法是:public CopyOnWriteArrayList() {setArray(new Object[0];)},就是设置了一个空数组。
  6. 保证线程安全的思路有?
    答:

    • 一种是,使用 synchronizedReentrantLock
    • 一种是循环 CAS
    • 一种是写时复制
      • 锁和循环 CAS 都是控制对同一个资源的访问冲突,而写时复制通过复制资源减少冲突。
      • 对于绝大部分访问都是读,且有大量并发线程要求读,只有个别线程进行写,且只是偶尔写的场合,写时复制就是一种很好的解决方案。
  7. CopyOnWriteArraySet 的原理?
    答:

    • CopyOnWriteArraySet 实现了 Set 接口,不包含重复元素,使用比较简单。
    • CopyOnWriteArraySet 内部是通过 CopyOnWriteArrayList 实现的
    • Set 的实现类如 HashSet/TreeSet 相比,CopyOnWriteArraySet 的性能比较低,不适用与元素个数特别多的集合。如果元素个数比较多,可以考虑 ConcurrentHashMapConcurrentSkipListSet
    • CopyOnWriteArrayListCopyOnWriteArraySet 适用于读远多于写、集合不太大的场合,它们采用了写时复制,这是计算机程序中一种重要的思维和技术