• Java集合:CopyOnWriteArrayList与SynchronizedList


    CopyOnWriteArrayList

    首先提两点:

    1、CopyOnWriteArrayList位于java.util.concurrent包下,可想而知,这个类是为并发而设计的

    2、CopyOnWriteArrayList,顾名思义,Write的时候总是要Copy,也就是说对于CopyOnWriteArrayList,任何可变的操作(add、set、remove等等)都是伴随复制这个动作的,后面会解读CopyOnWriteArrayList的底层实现机制

    四个关注点在CopyOnWriteArrayList上的答案

    关注点 结论
    允许空
    允许重复数据
    是否有序 有序
    线程安全 安全

    如何向CopyOnWriteArrayList中添加元素 

    对于CopyOnWriteArrayList来说,增加、删除、修改、插入的原理都是一样的,所以用增加元素来分析一下CopyOnWriteArrayList的底层实现机制就可以了。

    先看一下 CopyOnWriteArrayList 的结构:

    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8673264195747942595L;
    
        /** The lock protecting all mutators */
        final transient ReentrantLock lock = new ReentrantLock();
    
        /** The array, accessed only via getArray/setArray. */
        private transient volatile Object[] array;
        
        .....
    }

    构造方法

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    看到,对于CopyOnWriteArrayList来说,底层就是一个Object[] array,然后实例化一个CopyOnWriteArrayList,Object array指向一个数组大小为0的数组。

    添加元素

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 获取数组
            Object[] elements = getArray();
            int len = elements.length;
            // 通过复制生成一个新的数组,长度为原来长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 在最后添加元素
            newElements[len] = e;
            // 重新设置数组
            setArray(newElements);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    可以看出,每次添加元素,CopyOnWriteArrayList都要进行复制数组,代价十分昂贵。

    读的时候是不需要加锁的,直接获取。删除和增加是需要加锁的。

    有两点必须讲一下。我认为CopyOnWriteArrayList这个并发组件,其实反映的是两个十分重要的分布式理念:

    (1)读写分离

    我们读取CopyOnWriteArrayList的时候读取的是CopyOnWriteArrayList中的Object[] array,但是修改的时候,操作的是一个新的Object[] array,读和写操作的不是同一个对象,这就是读写分离。这种技术数据库用的非常多,在高并发下为了缓解数据库的压力,即使做了缓存也要对数据库做读写分离,读的时候使用读库,写的时候使用写库,然后读库、写库之间进行一定的同步,这样就避免同一个库上读、写的IO操作太多

    (2)最终一致

    对CopyOnWriteArrayList来说,线程1读取集合里面的数据,未必是最新的数据。因为线程2、线程3、线程4四个线程都修改了CopyOnWriteArrayList里面的数据,但是线程1拿到的还是最老的那个Object[] array,新添加进去的数据并没有,所以线程1读取的内容未必准确。不过这些数据虽然对于线程1是不一致的,但是对于之后的线程一定是一致的,它们拿到的Object[] array一定是三个线程都操作完毕之后的Object array[],这就是最终一致。最终一致对于分布式系统也非常重要,它通过容忍一定时间的数据不一致,提升整个分布式系统的可用性与分区容错性。当然,最终一致并不是任何场景都适用的,像火车站售票这种系统用户对于数据的实时性要求非常非常高,就必须做成强一致性的。

    最后总结一点,随着CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代价将越来越昂贵,因此,CopyOnWriteArrayList适用于读操作远多于修改操作的并发场景中

    Collections.SynchronizedList

    它是Collections中的一个静态内部类,它继承于Collections中的一个另外一个内部类:SynchronizedCollection,可以通过Collections.synchronizedList(List<T> list)方法获取,因为需要传入可以List的实例,因此它可以把一个非安全的List实例转换为一个安全的List。

    首先我们看一下SynchronizedCollection的结构:

    static class SynchronizedCollection<E> implements Collection<E>, Serializable {
            private static final long serialVersionUID = 3053995032091335093L;
    
        final Collection<E> c;  // Backing Collection
        final Object mutex;     // 相当于同步锁
    
        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            // 锁初始化为this
            mutex = this;
        }
        
        .....
    }

    在它的添加和删除方法中都加入同步代码块

    public boolean add(E e) {
        synchronized (mutex) {
           return c.add(e);
        }
    }
    public boolean remove(Object o) {
        synchronized (mutex) {
            return c.remove(o);
        }
    }

    因此,在Collections类中,其实就是简单地利用了同步代码块,原来线程不安全的集合,通过继承其内部类SynchronizedCollections,然后各自在重写一些自己的方法,达到线程安全。

  • 相关阅读:
    反思二
    安装Electron时卡在install.js不动的解决方案
    解决npm 下载速度慢的问题
    覆盖第三方jar包中的某一个类。妙!!
    关于拦截器是用注解方便,还是用配置文件写死方便的总结。
    yapi 启动后,老是自动关闭的问题。
    BaseResponse公共响应类,与我的设计一模一样,靠、ApiResponse
    HashMap 的 7 种遍历方式与性能分析!(强烈推荐)、forEach
    Jackson objectMapper.readValue 方法 详解
    yapi tag的问题,暂时只保留一个tag
  • 原文地址:https://www.cnblogs.com/yn-huang/p/10684713.html
Copyright © 2020-2023  润新知