zoukankan      html  css  js  c++  java
  • Java 经典面试题:聊一聊 JUC 下的 CopyOnWriteArrayList

    ArrayList 是我们常用的工具类之一,但是在多线程的情况下,ArrayList 作为共享变量时,并不是线程安全的。主要有以下两个原因:

    • 1、 ArrayList 自身的 elementData、size、modCount 在进行操作的时候,都没有加锁;
    • 2、这些变量没有被 volatile 修饰,在多线程的情况下,对这些变量操作可能会出现值被覆盖的情况;

    如果我们想在多线程情况下使用 ArrayList 怎么办?有以下几种办法:

    • 使用 Collections.SynchronizedList ;
    • 使用 JUC 下的 CopyOnWriteArrayList;

    先来看看 SynchronizedLis,Collections 其实就是对 ArrayList 进行了一个加锁包装,这个从源码中可以看出;

    ...部分源码,完整源码请查看 JDK 源码...
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);}
    }
    

    对于 Collections.SynchronizedList 比较简单,就是锁包装了一下,就不多说了~

    CopyOnWriteArrayList 也是 JUC 下面的一个并发容器类。不知道你发现没有,但凡你常用的集合类,在 JUC 下基本上都可以找到一个并发类,比如 hashMap 有对应的 ConcurrentHashMap。

    CopyOnWriteArrayList 跟 ArrayList 在整体架构上并没有什么区别,底层都是基于数组实现的。不同的地方大概有两点:

    • 底层数组被 volatile 关键字修饰;
    • 对数组进行数据变更时加锁;

    CopyOnWriteArrayList 的加锁操作跟 Collections.SynchronizedList 简单的加锁还不一样,CopyOnWriteArrayList 中的加锁过程还是非常值得学习的。CopyOnWriteArrayList 的加锁过程,大概可以概括为以下四步:

    • 1、加锁;
    • 2、从原数组中拷贝出新数组;
    • 3、在新数组上进行操作,并把新数组赋值给数组容器;
    • 4、解锁;

    结合源码来深入了解 CopyOnWriteArrayList 的并发实现,我们选择 ArrayList 最简单的将元素新增数组尾部的操作来分析实现过程,源码如下:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#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();
        }
    }
    

    CopyOnWriteArrayList 就是通过加锁来说实现容器安全的,可能你会有疑问,为什么引入一个新数组,数组的拷贝还是消耗时间的,直接在原数组上操作不就好了吗?。主要原因有以下两点:

    • volatile 关键字修饰的是数组,如果我们简单的在原来数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。
    • 在新的数组上进行拷贝,对老数组没有任何影响,只有新数组完全拷贝完成之后,外部才能访问到,降低了在赋值过程中,老数组数据变动的影响。比如经典的 ConcurrentModificationException 异常问题。

    其他的新增方法就自己去查看源码了,相差不多,基本上是一样的。对数组的删除跟新增都是差不多,不同的地方是在删除了时候,赋值给新数组时会出现不同的选择策略。我把源码贴上:

    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            // 先计算出要移动的问题
            int numMoved = len - index - 1;
            // 根据移动的位置选择策略
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
           //解锁
            lock.unlock();
        }
    }
    

    CopyOnWriteArrayList 还有其他的方法,在这里我就不过多介绍了。根据你们自己的疑问去扒一扒 CopyOnWriteArrayList 的源码就知道了,总体来说 CopyOnWriteArrayList 并不难,甚至感觉比 ArrayList 要简单。

    总结一下:CopyOnWriteArrayList 是安全的并发容器,有以下两个特点:

    • 1、对数组的写操作加锁,读操作不加锁;
    • 2、通过加锁 + 数组拷贝+ volatile 来保证线程安全;

    欢迎关注公众号【互联网平头哥】,一起成长,一起进步~。

    互联网平头哥

  • 相关阅读:
    Proj THUDBFuzz Paper Reading: The Art, Science, and Engineering of Fuzzing: A Survey
    Proj THUDBFuzz Paper Reading: A systematic review of fuzzing based on machine learning techniques
    9.3 付费代理的使用
    11.1 Charles 的使用
    第十一章 APP 的爬取
    10.2 Cookies 池的搭建
    10.1 模拟登录并爬取 GitHub
    11.5 Appium 爬取微信朋友圈
    11.4 Appium 的基本使用
    11.3 mitmdump 爬取 “得到” App 电子书信息
  • 原文地址:https://www.cnblogs.com/jamaler/p/12843126.html
Copyright © 2011-2022 走看看