【面试系列】并发容器之CopyOnWriteArrayList

微信公众号:放开我我还能学

分享知识,共同进步!java

image

ArrayList 有什么缺点?
  1. 非线程安全
  2. 迭代时没法修改
你用过线程安全的集合吗?

有,说在哪使用。数组

没有,不过我了解过。安全

那你说说它们的实现。

Vector微信

Vector 自己比较低效,由于它的实现基本就是将 add、get、set 等各类方法加上 synchronized 锁。这就致使了全部并发操做都要竞争同一把锁,一个线程在进行同步操做时,其余线程只能等待,大大下降了并发操做的效率。并发

Collections#SynchronizedList高并发

同步包装器 SynchronizedList 虽然没使用方法级别的 synchronized 锁,可是使用了同步代码块的形式,本质上仍是没有改进。spa

CopyOnWriteArrayList线程

CopyOnWriteArrayList 是一个写时复制的容器,当咱们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,而后往新的容器里添加元素,添加完元素以后,再将原容器的引用指向新的容器。这样作的好处是咱们能够对 CopyOnWriteArrayList 进行并发的读,而不须要加锁,由于当前容器不会添加任何元素。因此 CopyOnWriteArrayList 是一种读写分离的容器,适用于读多写少的场景,支持高并发读取。code

CopyOnWriteArrayList 是如何保证写时线程安全的?

使用了 ReentrantLock 独占锁,保证同时只有一个线程对集合进行修改操做。对象

如何理解 CopyOnWrite 思想?

写时复制。就是在写的时候,拷贝一份原对象,只操做拷贝的对象,操做完后再覆盖原对象,保证 volatile 语义。

CopyOnWriteArrayList 的缺点是什么?
  1. 占用内存问题。由于 CopyOnWrite 的写时复制机制,因此在进行写操做的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象,这样若是数据很大可能形成频繁的 GC。
  2. 数据一致性问题。由于 CopyOnWrite 容器支持读写分离,因此只能保证数据的最终一致性,不能保证明时一致性。
CopyOnWriteArrayList 在使用迭代器时是否有什么注意事项?

咱们知道,CopyOnWriteArrayList 是底层使用一种安全失败机制。也就是说,能够在迭代时进行增删改操做。

不过,在迭代器使用时有一个注意事项:迭代器获取的数据取决于迭代器建立的时候,而不是迭代器迭代的时候。

请看下面示例:

public static void main(String[] args) {
    CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1, 2, 3});

    ListIterator<Integer> iterator1 = list.listIterator();
    list.add(4);
    ListIterator<Integer> iterator2 = list.listIterator();

    iterator1.forEachRemaining(System.out::print);  // 123
    System.out.println();
    iterator2.forEachRemaining(System.out::print);  // 1234
}

从上能够看出,iterator1 在添加数据 4 以前就之前建立,那么最终它遍历获取的数据是 123,而 iterator2 在添加完数据 4 以后才建立,那么最终它遍历获取的数据是 1234。

下面我说下缘由。

image-20200504213216412

image-20200504213358237

无论是调用 iterator 方法获取迭代器,仍是调用 listIterator 方法获取迭代器,内部都会返回一个 COWIterator 对象。

image-20200504213546862

进入 COWIterator 构造方法查看,发如今构造方法中会把 array 数组赋值给 snapshot 变量,若是其余线程没有对 CopyOnWriteArrayList 进行增删改的操做,那么 snapshot 就是自己的 array,可是若是其余线程对 CopyOnWriteArrayList 进行了增删改的操做,那么旧的数组会被新的数组给替换掉,可是 snapshot 仍是原来旧的数组的引用。也就是说,当咱们使用迭代器遍历获取数据时,不能保证拿到的数据是最新的。

image

获取更多最新文章,关注公众号【放开我我还能学】

image

相关文章
相关标签/搜索