CopyOnWriteArrayList源代码阅读!

基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器。
这样做的好处:实现对 CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作,读写分离。

从JDK1.5 开始Java 并发包里提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteArrayList和 CopyOnWriteArraySet。
CopyOnWriteArrayList 相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用 ReentrantLock 实现,允许多个线程并发读取,但只能有一个线程写入

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
	//	定义了一个锁,即对写入(增加,删除,修改)使用的,
	//	保护写入时线程安全,因为只有一把锁,所以有一个线程在进行写入操作时,
	//	其他的写入操作也不能被其他线程使用
    final transient ReentrantLock lock = new ReentrantLock();
 
    private transient volatile Object[] array;

set()方法

//给数组的指定索引位置添加元素
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//获取原数组
            Object[] elements = getArray();
            //获得原数组中目标索引位置的元素
            E oldValue = get(elements, index);
            //判断需要添加的元素在原数组中是否存在
            if (oldValue != element) {
            	//获取原数组的长度
                int len = elements.length;
                //根据原数组Arrays工具类copyOf()方法复制一个新的数组,数组长度与原数组长度一致
                Object[] newElements = Arrays.copyOf(elements, len);
                //给新数组的目标索引位置添加新的元素
                newElements[index] = element;
                setArray(newElements);
            } else {
            	//如果需要添加的元素在原数组中存在,则直接赋值给新数组
                setArray(elements);
            }
            return oldValue;   
        } finally {
            lock.unlock();  //手动释放锁
        }
    }

 

add()方法

    public boolean add(E e) {
         //上锁操作,保证写操作的原子性
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //getArray()方法是获取到内部维护的数组
            Object[] elements = getArray();

            // 获取数组的长度
            int len = elements.length;

            // 通过Arrays工具类的copyOf()方法复制出一个新的数组,并且长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);

            // 新数组的最后一位的赋要增加的值
            newElements[len] = e;
            
            // 将新数组赋值到内部维护的数组中
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();   //手动释放锁
        }
    }

 

 add()重载方法

   public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //判断要添加新元素的索引位置是否超过数组长度或则小于0
            if (index > len || index < 0)
            	//如果超过则抛出异常
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;  
            //如果没超过就再判断是否是数组长度最后一位的索引
            if (numMoved == 0)
            	//是最后一位就复制一个新的数组并长度加1
                newElements = Arrays.copyOf(elements, len + 1);
            else {
            	//不是最后一位就将原有数组的索引位置前所有的内容复制到新数组
                //再将原有数组索引位置后所有的内容添加到新数组的索引位置+1位置的后面
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //给新数组的目标索引赋值
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

remove()方法

  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)
            	//如果是最后以为就复制一个新的数组并长度-1
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            	//如果不是最后一位,则需要新建一个原数组长度-1的新数组
            	//将原数组从索引位置0开始复制到目标索引位置给新数组
            	//再将原数组中目标索引位置+1开始复制到新数组的目标位置后面
                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();
        }
    }

 

removeRange()方法 

//删除指定索引范围内的数据
    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;
            //判读结束索引位置是否是最后一位
            if (numMoved == 0)
            	//如果是最后一位,则复制出新的数组,数组长度设为计算获得的长度
                setArray(Arrays.copyOf(elements, newlen));
            else {
            	//如果不是最后一位,则要建一个新的数组
            	//从原数组的索引位置0开始复制到新数组中(索引位置也从0开始)
            	//再将原数组中从结束索引位置开始复制到新数组的开始索引位置
                Object[] newElements = new Object[newlen];
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                setArray(newElements);
            }
        } finally {
            lock.unlock();
        }
    }

 

removeAll()方法 

     //删除数组中c中含有的数据
    public boolean removeAll(Collection<?> c) {
    	//如果C为空则抛出空指针异常
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            //获取原数组的长度
            int len = elements.length;
           //判断原数组是否为空
            if (len != 0) {
                int newlen = 0;
                Object[] temp = new Object[len];
                //将原数组的元素依次遍历
                //如果C中不包含该元素,便将该元素添加到新建的数组中
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                //判断新数据的长度是否和新数组的长度一样
                if (newlen != len) {
                	//如果不一样,则将根据新建的数组复制出新的数组
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;  //如果原数组为空返回false
        } finally {
            lock.unlock();
        }
    }

总结

  1. CopyOnWriteArrayList体现了写时复制的思想,增删改操作都是在复制的新数组中进行的。
  2. CopyOnWriteArrayList的增删改方法通过可重入锁确保线程安全;
  3. CopyOnWriteArrayList线程安全体现在多线程增删改不会抛出java.util.ConcurrentModificationException异常,并不能确保数据的强一致性。
  4. 同一时刻只能有一个线程对CopyOnWriteArrayList进行增删改操作,而读操作没有限制,并且 CopyOnWriteArrayList增删改操作都需要复制一份新数组,增加了内存消耗,所以CopyOnWriteArrayList适合读多写少的情况。
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CopyOnWriteArrayList是一个线程安全的List实现,它通过每次修改操作(添加、删除、修改)时都创建一个新的底层数组来实现线程安全性。 CopyOnWriteArrayList的特点如下: 1. 线程安全:多个线程可以同时读取CopyOnWriteArrayList的内容,而不需要额外的同步机制。这使得它非常适合在读操作远远多于写操作的场景中使用。 2. 写操作的代价较高:每次对CopyOnWriteArrayList进行写操作时,都会创建一个新的底层数组,因此写操作的代价较高。 3. 实时性较低:由于写操作会创建新的底层数组,读取操作可能会看到旧的数据,因此CopyOnWriteArrayList的实时性较低。 使用CopyOnWriteArrayList的示例代码如下: ```java import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Hello"); list.add("World"); for (String item : list) { System.out.println(item); } } } ``` 在上述代码中,我们创建了一个CopyOnWriteArrayList,并向其中添加了两个元素。然后使用增强for循环遍历CopyOnWriteArrayList中的元素,并打印输出。 需要注意的是,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,如果写操作非常频繁,可能会导致性能问题。此外,CopyOnWriteArrayList不保证元素的顺序性,因为在写操作时会创建新的底层数组。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值