• CopyOnWriteArrayList源码解析


    CopyOnWriteArrayListjava1.5版本提供的一个线程安全的ArrayList变体。

    在讲解5.1.1ArrayList的时候,有说明ArrayListfail-fast特性,它是指在遍历过程中,如果ArrayList内容发生过修改,会抛出ConcurrentModificationException

    在多线程环境下,这种情况变得尤为突出,参见测试代码(代码基于java8):

    ArrayList<Integer> list = new ArrayList<>();
    
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    for (int i = 0; i < 100; i++) {
    
    // 启动一个写ArrayList的线程
    
    executorService.execute(() -> {
    
    list.add(1);
    
    });
    
    // 启动一个读ArrayList的线程
    
    executorService.execute(() -> {
    
    for (Integer v : list) {
    
    System.out.println(v);
    
    }
    
    });
    
    }

    设想下面两种处理方式:

    1、不使用迭代器形式转而使用下标来遍历,这就带来了一个问题,读写没有分离,写操作会影响到读的准确性,甚至导致IndexOutOfBoundsException,比如下面的例子:

    上述例子在多线程执行过程中,list.remove(0)会减少listsize,而读操作使用的是首次遍历的size,必然会出现严重的运行时异常,所以,遍历下标的方法不可取。

                  executorService.execute(() -> {
    
              list.remove(0);
    
            });
    
            executorService.execute(() -> {
    
                for (int x = 0, len = list.size(); x < len; x++) {
    
                  System.out.println(list.get(x));
    
                }
    
            });

    2、不直接遍历list,而是把list拷贝一份数组,再行遍历,比如把读过程修改成下面这样:

    executorService.execute(() -> {
    
        for (Integer v : list.toArray(new Integer[0])) {
    
            System.out.println(v);
    
        }
    
    });

    此方法在CopyOnWriteArrayList出现之前较为常见,其本质是把list内容拷贝到了一个新的数组中,CopyOnWriteArrayList也是采取的类似的手段,区别在于,这个例子使用的是CopyOnRead方式,也就是读时拷贝

    下面来介绍下CopyOnWriteArrayList写时拷贝的实现方式。

    1.1.1.1.1 写时拷贝

    写时拷贝,自然是在做写操作时,把原始数据拷贝到一个新的数组,涉及到写操作的是三个方法addremoveset,以add方法为例:

    public boolean add(E e) {
    
        //加锁
    
            final ReentrantLock lock = this.lock;
    
            lock.lock();
    
            try {
    
                //拷贝数据
    
                Object[] elements = getArray();
    
                int len = elements.length;
    
                Object[] newElements = Arrays.copyOf(elements, len + 1);
    
                newElements[len] = e;
    
                setArray(newElements);
    
                return true;
    
            } finally {
    
                //解锁
    
                lock.unlock();
    
            }
    
        }

    可以注意到,在每一次add操作里,数组都被copy了一份副本,这就是写时拷贝的原理。

    那么,写时拷贝和读时拷贝各有什么优势呢?

    如果一个list的遍历操作比写入操作频繁,应该使用CopyOnWriteArrayList,反之,则考虑使用读时拷贝的方式

  • 相关阅读:
    快捷定位目录 z武器
    [UOJ317]【NOI2017】游戏 题解
    2-SAT 问题与解法小结
    link-cut-tree 简单介绍
    hihocoder #1456 : Rikka with Lattice(杜教筛)
    杜教筛小结
    BZOJ 2969: 矩形粉刷(期望)
    UVA10294 Arif in Dhaka (群论,Polya定理)
    BZOJ 1926: [Sdoi2010]粟粟的书架(主席树,二分答案)
    BZOJ 2683: 简单题(CDQ分治 + 树状数组)
  • 原文地址:https://www.cnblogs.com/anrainie/p/6929786.html
Copyright © 2020-2023  润新知