CopyOnWriteArrayList核心源码阅读

CopyOnWriteArrayList是一种线程安全的列表,适用于读多写少的场景。它通过写时复制策略确保在修改时创建副本,从而避免在并发环境下的数据不一致。文章详细介绍了CopyOnWriteArrayList的优缺点、适用场景,并对其核心源码进行了深度解析,包括add、set、remove等方法的实现。
摘要由CSDN通过智能技术生成


前言

什么是线程安全
线程安全是指在多线程环境下,对共享资源(如变量、对象、数据结构等)的访问操作能够正确地进行,不会导致不确定的结果或破坏数据的一致性。
在之前集合的学习中,ArrayList、LinkedList、HashMap等都是线程不安全的集合,在单线程环境下,程序的执行是按照顺序逐行执行的,因此不会出现多个线程同时访问和修改共享资源的情况。然而,在多线程环境下,多个线程可能同时访问和修改共享资源,这就引入了并发问题。
Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器
而CopyOnWriteArrayList就是一种线程安全的List集合。


一、CopyOnWriteArrayList的优缺点

  • 线程安全:CopyOnWriteArrayList是线程安全的,可以在多线程环境中使用而无需额外的同步措施。多个线程可以同时对CopyOnWriteArrayList进行读操作,而不会导致数据不一致或抛出并发修改异常。
  • 写时复制:当进行修改操作(如添加、修改、删除)时,CopyOnWriteArrayList会创建一个新的副本,并在副本上进行修改操作,而原始数据保持不变。这种写时复制的机制保证了读操作的高效性,因为读操作不需要加锁。
  • 内存占用:由于每次写操作都会复制整个数据集合,因此CopyOnWriteArrayList的内存占用较高。在处理大数据集合时,需要注意内存的消耗,并根据实际情况选择合适的数据结构。
  • 实时性:由于写操作不会阻塞读操作,因此读操作可能会读取到旧的数据。在需要实时性较高的场景下,CopyOnWriteArrayList可能不适合,可以考虑其他线程同步机制。

二、CopyOnWriteArrayList的适用场景

  • 读多写少的场景:由于CopyOnWriteArrayList的写操作会复制整个数组,因此适用于读多写少的场景。在这种场景下,读操作可以高效地并发执行,而写操作的开销相对较高,但由于写操作较少,整体性能仍然可接受。

  • 数据不频繁变动的场景:CopyOnWriteArrayList适合存储不经常变动的数据集合。如果数据集合的修改频率很高,每次修改都会触发全新的复制,这可能导致内存占用较大,影响性能。


三、CopyOnWriteArrayList核心源码阅读

1.实现接口及成员变量

CopyOnWriteArrayList类实现了List接口,表示一个List集合作为底层实现的,RandomAccess是一个标记接口,用于标识实现该接口的类支持随机访问;
Cloneable是一个标记接口,用于标识实现该接口的类可以进行克隆(复制)操作;
Serializable是一个标记接口,用于标识实现该接口的类可以进行序列化操作。

public class CopyOnWriteArrayList<E>
			implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //创建ReentrantLock对象lock 
    final transient ReentrantLock lock = new ReentrantLock();
     //声明底层数据结构是Object类型的数组
    private transient volatile Object[] array;

2.无参构造方法

默认初始化一个空的Object类型的数组

//调用了setArray()方法并传入了一个空的Object数组
public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
//将参数中的数组赋值给成员变量array
final void setArray(Object[] a) {
        array = a;
    }

3.add方法

  • add(E e)方法:添加一个元素
//获取当前 CopyOnWriteArrayList 实例的底层数组
final Object[] getArray() {
    return array;
 }
 public boolean add(E e) {
 		//通过ReentrantLock类定义锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
        	//获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
            //对原数组进行复制操作,复制的结果保存在一个新数组中
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //将添加的值e添加到新数组中
            newElements[len] = e;
            //将新数组赋值给底层数组
            setArray(newElements);
            //添加元素成功,返回true
            return true;
        } finally {
        	//释放锁
            lock.unlock();
        }
    }
  • add(int index, E element)方法:添加元素到指定位置
public void add(int index, E element) {
		//通过ReentrantLock类定义锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
        	//获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
            //判断传入下标是否合法
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            //定义新数组
            Object[] newElements;
            //需要移动的元素数量
            int numMoved = len - index;
            //如果需要移动的元素数量等于0,说明是在末尾添加元素
            if (numMoved == 0)
            	//将原数组和新长度复制给新数组
                newElements = Arrays.copyOf(elements, len + 1);
            else {
            	//新数组长度为原数组长度+1
                newElements = new Object[len + 1];
                //复制旧数组[0,index]的数据到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //复制旧数组从index开始复制numMoved个到新数组index+1开始复制numMoved;
                //其作用刚好空出了指定位置index来存放要加入的值
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //向新数组的指定位置赋值;
            newElements[index] = element;
            //将新数组赋值给底层数组
            setArray(newElements);
        } finally {
            lock.unlock();//释放锁
        }
    }
  • addAll(Collection<? extends E> c):将指定集合中的所有元素添加到列表中
 public boolean addAll(Collection<? extends E> c) {
 		//如果集合对象c的类信息等于CopyOnWriteArrayList的类信息,就对集合做强制
 		//类型转换,然后调用其 getArray() 方法来获取底层数组,否则,调用 toArray() 方法将集合转换为数组
        Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
            ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
         //如果数组cs的长度为0,表示没有要添加的元素,直接返回false
        if (cs.length == 0)
            return false;
            //定义锁
        final ReentrantLock lock = this.lock;
        lock.lock();//加锁
        try {
        	//获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
            //如果len长度为0并且cs的类型是Object[],
            if (len == 0 && cs.getClass() == Object[].class)
            	将cs数组赋值给底层数组
                setArray(cs);
            else {
            	//创建新数组newElements,长度为len + cs.length,并将elements数组的内容复制到新数组中
                Object[] newElements = Arrays.copyOf(elements, len + cs.length);
                //将cs数组的内容复制到newElements数组的末尾。
                System.arraycopy(cs, 0, newElements, len, cs.length);
                //将新数组赋值给底层数组
                setArray(newElements);
            }
            return true;
        } finally {
            lock.unlock();//释放锁
        }
    }

4.set方法

  • set(int index, E element):修改指定位置元素
 public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//获取当前对象的内部数组
            Object[] elements = getArray();
            //获取索引index位置的元素
            E oldValue = get(elements, index);
			//如果数组该位置的原始值和钥修改的值不一样
            if (oldValue != element) {
             	//得到数组长度
                int len = elements.length;
                //复制内容至新数组
                Object[] newElements = Arrays.copyOf(elements, len);
                //将要修改的值改入新数组中
                newElements[index] = element;
                //将新数组赋值给底层数组
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                //不做修改,将原数组赋值给底层数组
                setArray(elements);
            }
            return oldValue;//返回数组修改前的值
        } finally {
            lock.unlock();//释放锁
        }
    }

5.remove方法

  • remove(int index):按索引删除指定元素
 public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
            //获取索引index位置的元素
            E oldValue = get(elements, index);
            //表示要移动的元素数量
            int numMoved = len - index - 1;
            //如果要移动的元素数量为0,则表示删除的是最后一个元素
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            	//创建新数组newElements,长度为len-1
                Object[] newElements = new Object[len - 1];
                //将旧数组索引index之前的元素复制到newElements数组中
                System.arraycopy(elements, 0, newElements, 0, index);
                //再将就数组索引index+1之后的元素复制到newElements数组中
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                 //将新数组赋值给底层数组               
                setArray(newElements);
            }
            return oldValue;//返回删除的元素
        } finally {
            lock.unlock();
        }
    }
  • removeRange(int fromIndex, int toIndex):删除指定范围元素
void removeRange(int fromIndex, int toIndex) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
       		 //获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
			//判断参数是否合法
            if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
                throw new IndexOutOfBoundsException();
            //移除指定范围后的新长度
            int newlen = len - (toIndex - fromIndex);
            //移动的元素数量
            int numMoved = len - toIndex;
            //如果要移动的元素数量为0,则表示删除的是数组的末尾
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, newlen));
            else {
            	//创建新数组newElements,长度为移除指定范围后的新长度
                Object[] newElements = new Object[newlen];
                //将旧数组索引fromIndex之前的元素复制到newElements数组中
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                //将旧数组索引toIndex之后的元素复制到newElements数组中
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                //将新数组赋值给底层数组  
                setArray(newElements);
            }
        } finally {
            lock.unlock();
        }
    }
  • removeAll(Collection<?> c):移除指定 collection 中的所有元素
public boolean removeAll(Collection<?> c) {
		//判断传参是否合法
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
       		 //获取当前对象的内部数组
            Object[] elements = getArray();
            //得到数组长度
            int len = elements.length;
            //数组长度不为0
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                //创建新数组temp ,长度为len;
                Object[] temp = new Object[len];
                //通过循环做过滤操作,如果该元素不在要删除的集合中,则添加至数组temp中
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                //如果不相等,表示有元素需要被移除
                if (newlen != len) {
                	//将temp数组中前newlen个元素复制到新数组中,并将该数组赋值给底层数组
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;//删除成功,返回true
                }
            }
            //如果长度为0,直接返回false表示没有进行移除操作
            return false;
        } finally {
            lock.unlock();
        }
    }

6.get方法

  • get(int index):根据指定下标获取元素
 public E get(int index) {
 		//get(Object[] a, int index)方法
 		//getArray()获取当前对象的内部数组
        return get(getArray(), index);
    }
  • get(Object[] a, int index):根据指定下标在指定数组中获取元素
 @SuppressWarnings("unchecked")
 	//通过下标在数组中获取元素
    private E get(Object[] a, int index) {
        return (E) a[index];
    }   

7.sort方法

  • sort(Comparator<? super E> c):排序方法;需要传入Comparator接口的实现类,确定比较规则
 public void sort(Comparator<? super E> c) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
      		//获取当前对象的内部数组
            Object[] elements = getArray();
            //将elements数组的内容复制到newElements数组中
            Object[] newElements = Arrays.copyOf(elements, elements.length);
            //将newElements数组转换为泛型数组E[] es
            @SuppressWarnings("unchecked") E[] es = (E[])newElements;
            //根据传入的实现类,对数组进行排序
            Arrays.sort(es, c);
            //将新数组赋值给底层数组 
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

8.clear方法

  • clear():清空集合中的所有元素
public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//将一个长度为0的新数组赋值给底层数组 
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }

9.equals方法和hashCode方法

  • equals(Object o):判断当前对象是否与o相等
public boolean equals(Object o) {
		//引用数据类型==比较内存地址,内存地址相同,直接返回true
        if (o == this)
            return true;
        //instanceof判断对象o是否是List的实例,不是返回false
        if (!(o instanceof List))
            return false;
		//将给定对象o转换为List类型
        List<?> list = (List<?>)(o);
        //获取List对象的迭代器it
        Iterator<?> it = list.iterator();
        //获取当前对象的内部数组
        Object[] elements = getArray();
        //得到数组长度
        int len = elements.length;
        
        for (int i = 0; i < len; ++i)
            if (!it.hasNext() || !eq(elements[i], it.next()))
            //在!it.hasNext()循环未结束,迭代器it没有下一个元素了,元素长度不同,返回false
            //!eq(elements[i], it.next():判断当前元素与迭代器返回的元素,不同,则返回false
                return false;
         //循环结束,迭代器it还有下一个元素,元素长度不同,返回false
        if (it.hasNext())
            return false;
        return true;
    }
  • hashCode():计算当前对象的哈希码值
public int hashCode() {
        int hashCode = 1;
        //获取当前对象的内部数组
        Object[] elements = getArray();
        //得到数组长度
        int len = elements.length;
       
        for (int i = 0; i < len; ++i) {
        	 //将数组中的每个依次赋值给obj变量。
            Object obj = elements[i];
            //如果obj不为null,用31*hashCode+obj.hashCode()进行计算;
            //如果obj为null,用31*hashCode+0进行计算;
            hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
        }
        //循环结束后,将计算好的hashCode返回
        return hashCode;
    }

总结

CopyOnWriteArrayList为开发者提供了一种简单而有效的线程安全集合,可以在并发环境中安全地访问和修改数据,通过写时复制的机制保证了读操作的高效性。它适用于读多写少、数据不频繁变动的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值