CopyOnWriteArrayList原理

简介: 文章主要讨论了CopyOnWriteArrayList的工作原理。CopyOnWriteArrayList通过“写时复制”的策略和ReentrantLock锁来保证线程安全性,适用于读多写少的场景;该实现牺牲了一定的写入性能(因为每次写入都需要复制整个数组),但显著提高了读取性能;在多线程环境中,特别是读取操作远多于写入操作时,CopyOnWriteArrayList是一个非常有效的选择。

本文知识需要理解之前的文章ReentrantLock知识才能更好的理解CopyOnWriteArrayList的原理,因为它是基于ReentrantLock来实现并发安全的。

ArrayList不是一个线程安全的容器,多线程并发操作会造成数据不一致,Jdk官方提供了解决方案:CopyOnWriteArrayList,它是一个并发安全的容器,从名字可以看出来,如果要修改这个集合的时候,会拷贝一份出来修改,然后替换原理的数组。 下面我们通过查看源码来验证是否是这样实现的。

add方 法:

//定义了一把全局锁final transient ReentrantLock lock = new ReentrantLock();​//一个volatile修饰的数组,保证多线程之间可见。    /** The array, accessed only via getArray/setArray. */private transient volatile Object[] array;    //以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源码看出,操作数组时先获取全局锁,再从旧数组拷贝数据到新数组,最后将新数组替换老数组,这样不会直接修改老数组里的数据。

get方法:

    private E get(Object[] a, int index) {
           return (E) a[index];    }/**     * {@inheritDoc}     *     * @throws IndexOutOfBoundsException {@inheritDoc}     */    public E get(int index) {
           return get(getArray(), index);    }​    final Object[] getArray() {
           return array;    }

通过get方法看出,get方法是直接读数组里的数据,也符合

CopyOnWriteArrayList类名的意思,只有写才会拷贝旧数组。

迭代器源码实现:

static final class COWIterator<E> implements ListIterator<E> {
           /** 快照数据 Snapshot of the array */        private final Object[] snapshot;        /** Index of element to be returned by subsequent call to next.  */        private int cursor; }  public Iterator<E> iterator() {
           return new COWIterator<E>(getArray(), 0); }

迭代器类使用了快照技术,不直接迭代原数组,因此不会有ConcurrentModificationException异常。

CopyOnWriteArrayList应用举例:

public class DriverManager {    // Jdbc驱动存储到CopyOnWriteArrayList    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();}

总结一下:

CopyOnWriteArrayList 是线程安全的并发容器,多线程操作列表的场景可以使用,读多写少,有以下两个特点:

1、对数组的写操作加锁,读操作不加锁;

2、通过加锁 + 数组拷贝(空间换时间)+ volatile 来保证线程安全;

CopyOnWriteArrayList还是比较简单的,它利用了ReentrantLock技术,

java.util.concurrent并发包中有很多并发安全的容器都借助了

ReentrantLock技术来实现,比如阻塞队列BlockingQueue,闭锁CountDownLatch等等,后面文章将会一一来分析。

相关文章
|
7月前
|
安全 算法 Java
剑指JUC原理-19.线程安全集合(上)
剑指JUC原理-19.线程安全集合
56 0
|
存储 安全 Java
ConcurrentHashMap底层实现原理
ConcurrentHashMap底层实现原理
294 0
|
7月前
|
编解码 安全 算法
Java多线程基础-18:线程安全的集合类与ConcurrentHashMap
如果这些单线程中的集合类确实需要在多线程中使用,该怎么办呢?思路有两个: 最直接的方式:使用锁,手动保证。如多个线程修改ArrayList对象,此时就可能有问题,就可以给修改操作进行加锁。但手动加锁的方式并不是很方便,因此标准库还提供了一些线程安全的集合类。
111 4
|
6月前
|
存储 设计模式 并行计算
CopyOnWriteArrayList:深入理解Java中的线程安全List原理和应用
CopyOnWriteArrayList:深入理解Java中的线程安全List原理和应用
|
7月前
|
存储 安全 Java
Java并发基础:CopyOnWriteArrayList全面解析
CopyOnWriteArrayList类的最大优点在于读取时无需加锁,非常适合读多写少的并发场景,由于其写操作通过复制底层数据来实现,从而保证了读取数据的一致性和高效性,此外,它简单易用,是快速实现线程安全列表的不错选择,CopyOnWriteArrayList在读操作占主导的场景下,能够提供出色的性能和稳定性。
154 1
Java并发基础:CopyOnWriteArrayList全面解析
|
7月前
|
存储 安全 Java
CopyOnWriteArrayList底层原理全面解析【建议收藏】
CopyOnWriteArrayList是Java中的一个线程安全的集合类,是ArrayList线程安全版本,主要通过Copy-On-Write(写时复制,简称COW)机制来保证线程安全。 Copy-On-Write机制核心思想:向一个数组中添加数据时,不直接操作原始数组,而是拷贝原始数组生成一份原始数组副本,将需要添加的数据添加到原始数组副本中,操作完成后再用原始数组副本直接替换原始数组,从而保证多个线程同时操作原始数组时的线程安全。
|
7月前
|
安全 Java 数据库
剑指JUC原理-19.线程安全集合(下)
剑指JUC原理-19.线程安全集合
56 0
|
安全 Java 索引
JUC第十六讲:JUC集合: CopyOnWriteArrayList详解
JUC第十六讲:JUC集合: CopyOnWriteArrayList详解
|
存储 机器学习/深度学习 算法
源码剖析之ConcurrentHashMap
​ JDK8中ConcurrentHashMap的结构是:数组+链表+红黑树。 ​ 因为在hash冲突严重的情况下,链表的查询效率是O(n),所以jdk8中改成了单个链表的个数大于8时,数组长度小于64就扩容,数组长度大于等于64,则链表会转换为红黑树,这样以空间换时间,查询效率会变O(nlogn)。 ​ 红黑树在Node数组内部存储的不是一个TreeNode对象,而是一个TreeBin对象,TreeBin内部维持着一个红黑树。 ​ 在JDK8中ConcurrentHashMap最经点的实现是使用CAS+synchronized+volatile 来保证并发安全
126 0
源码剖析之ConcurrentHashMap
|
存储 安全 算法
Java并发编程之ConcurrentHashMap源码分析
HashMap多线程put后get为null和多线程put的时候可能导致元素丢失 在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap
256 0
Java并发编程之ConcurrentHashMap源码分析