【多线程】CopyOnWriteList详解

简介

JDK1.5之前想要并发使用官方提供的线程安全的List只能选择Vector,但是Vector类中增删改查等方法都加了synchronized关键字,虽然能够保证同步,但是因为同一个实例多个线程竞争一把锁,导致性能非常低下。
JDK1.5之后引入的JUC包中提供了一个CopyOnWriteArrayList集合对象,该对象是一个线程安全的对象,并且不想Vector类一样一把大锁,CopyOnWriteArrayList在并发读写上效率高出一大截。

CopyOnWriteArrayList适用场景

适用业务场景是读多写少,并且能接受一定程度的数据不一致。一般我们会有一级二级缓存等来应对高并发情况下的读。基于此场景JDK提供了数据结构CopyOnWriteArrayList。在读的时候不进行加锁,写的时候才加锁保证线程安全。
这种思路与ReentrantReadWriteLock读写锁设计思路类似,但是CopyOnWriteArrayList更加极端,读读不互斥、读写不互斥(ReentrantReadWriteLock 的读写互斥)、写写互斥。这样以来是的并发情况下的读操作性能大幅度提升。

源码分析

CopyOnWriteArrayList的线程安全核心是在增删改的时候进行同步复制,从而保证读写不互斥。

  • 进行addsetremove等操作时,CopyOnWriteArrayList不会修改之前的数组对象,而是在原有的基础上进行数组拷贝,在新拷贝出来的对象中进行增删改,再将修改完的数组赋值回去;
  • 进行getiterator 等获取的操作时,是直接获取当前数组的对象副本,然后在此副本的基础上进行数据的获取或者遍历。
    //jkd 17 新增
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }
    // 获取当前数组
    final Object[] getArray() {
        return array;
    }
   //读取
    public E get(int index) {
        return elementAt(getArray(), index);
    }
    static <E> E elementAt(Object[] a, int index) {
        return (E) a[index];
    }
  • JDK8中使用的是ReentrantLock 来做的加锁和解锁,JDK17中使用synchronized 代码块进行的加锁和解锁。
  • 从源码上来看使用synchronized 代码块保证了同步,避免了多线程写的时候复制出多个副本,这样在写完之后将新值赋值给数组也不会影响当前正在执行的读取操作,因为读本身自己也有一个数组副本。
  • 读操作在此时是弱一致性的,部分场景下有可能读取到旧的元素值
    • 线程A调用get(int index)方法,内部调用getArray()获得副本
    • 线程B调用add(Object obj),内部通过setArray修改了array的值
    • 线程A这时获取的值还是副本中的就值

常用的方法

  • 加锁的方法
    • add 及其他add相关
    • remove 及其他remove相关
    • clear
  • 无锁的方法
    • get
    • contains
    • containsAll
    • size

CopyOnWriteArrayList的缺点

  • 因为写操作时进行数组赋值,所以会额外的内存占用,在数据量很大的情况下,会导致内存资源不足;
  • 每一次写操作都会进行复制/修改/替换,频繁写的场景下性能会有很大影响;
  • 因为读写不互斥,所以存在一定的数据一致性问题。

能不能在写的时候不进行复制,在保证同步时直接像ArrayList一样进行数组的添加,然后读取时不加锁直接读数组中的值,这样即没有复制操作,也能保证写同步,读写无锁,保证高性能?

结论:不能
如果不进行复制或者副本的备份进行操作,就会导致List对象在并发情况下调用iterator等方法因为数组长度发生变化而导致快速失败。不能保证业务的正常进行

posted @ 2025-03-05 16:17  此木|西贝  阅读(82)  评论(0)    收藏  举报