欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CopyOnWriteArrayList分析

程序员文章站 2022-07-14 16:10:11
...

引用源码的注释

This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads.

CopyOnWriteArrayList的实现很简单,主要是理解为什么要用它。上面已经说了在遍历操作远多于添加和删除元素操作时可以使用CopyOnWriteArrayList。注意这里只涉及3种操作:遍历操作、添加元素和删除元素。这里甚至都没有处理并发修改元素值时可能遇到的问题,所以显然CopyOnWriteArrayList的是为了解决上面那三种并发时可能产生的问题,下面解释究竟解决了什么问题。

通常在遍历数组时有两种方式

  1. 通过Iterator遍历,foreach 循环遍历list时也是使用的Iterator。这种方式遍历时会产生ConcurrentModificationException异常。
  2. 通过索引遍历,foreach 循环遍历数组时使用索引遍历方法,这种方式遍历时会产生IndexOutOfBoundsException异常。

因此,如果想要使遍历和修改数组长度的的操作在并发时不产生异常,只能使用同步。一般情况下遍历操作、添加元素以及删除元素都需要加锁。但是在遍历操作远多于添加和删除元素操作时,对读操作加锁会导致很大的性能影响,因此采用CopyOnWriteArrayList可以保证读操作不加锁。

但是CopyOnWriteArrayList也不是免费的解决方案,它有两个缺点:

  1. 遍历操作会读到失效数据
  2. 复制会导致大量的内存消耗
public class COWListTest {

    public static void main(String args[]) throws InterruptedException {
        CopyOnWriteArrayList<Integer> COWList =
                IntStream.iterate(0,a->a+1)
                        .limit(10)
                        .boxed()
                        .collect(toCollection(CopyOnWriteArrayList::new));
        Thread thread1 = new Thread(() -> COWList.remove(5));
        Thread thread2 = new Thread(() -> {
            // 1、通过迭代器遍历
            for(Integer val : COWList)
                System.out.println(val);
            ///2、通过索引遍历
            for (int i = 0; i < 10; i++)
                System.out.println(COWList.get(i));
        });
        thread2.start();
        thread1.start();
        thread1.join();
        thread2.join();

    }
}