CopyOnWriteArrayList核心源码阅读

目录

概述

线程安全同步锁

存储元素的容器

获取当前集合的数组,设置当前集合的引用

无参构造方法

get(Object[] a, int index)

get(int index)

set(int index, E element)

add(E e)

add(int index, E element)

remove(int index)

removeRange(int fromIndex, int toIndex)

removeAll(Collection c)

clear() 

addAll(Collection c)

sort(Comparator c)

equals(Object o)

hashCode()

总结


概述

Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器

这样做的好处:实现对CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作。所以CopyOnWrite容器也是一种读写分离的思想。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。

CopyOnWriteArrayList相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用ReentrantLock实现,允许多个线程并发读取,但只能有一个线程写入

该类中对一些操作方法进行了改进,使写操作的方法具有了线程安全的机制,同时允许多个线程同时读取,下面是常用方法的源代码阅读:

线程安全同步锁

使用了ReentrantLock类实现了线程安全。

// 使用ReentrantLock类实现线程同步安全,并使用final关键字使它不可再指向新的引用
final transient ReentrantLock lock = new ReentrantLock();

存储元素的容器

内部通过数组充当集合的容器。

 // 存储数据的基本数组
private transient volatile Object[] array;

获取当前集合的数组,设置当前集合的引用

final Object[] getArray() {
        return array;
    }
   
final void setArray(Object[] a) {
        array = a;
    }

无参构造方法

创建对象时,调用setArray()方法使其指向一个新数组。

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

get(Object[] a, int index)

    // 获取数组中指定下标的元素
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

get(int index)

    // get()的重载方法,传入下标获取指定下标位置上的元素
    public E get(int index) {
        // 调用重载方法,返回数组中指定位置的元素	
        return get(getArray(), index);
    }

set(int index, E element)

// 设置指定下标位置的元素并返回oldValue(修改前元素)
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock; // 拿到ReentrantLock对象当锁
        lock.lock(); // 给代码块加锁

        // 防止中断或其他异常使代码块中的程序不能正常结束,将解锁的步骤放在finally块中,使其可以释放锁
        try {
            Object[] elements = getArray(); 
            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();
        }
    }

add(E e)

    // 添加元素至尾部并返回操作成功与否的布尔值
    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); // 复制出一个长度加1的数组
            newElements[len] = e; // 将新元素添加至末尾
            setArray(newElements); // 指向新的数组
            return true; // 操作成功返回true
        } finally {
            lock.unlock(); // 确保代码块可以释放锁
        }
    }

add(int index, E element)

在指定位置上添加元素。

  1. 复制出一个长度+1的新数组;
  2. 并将原数组拆分为指定位置的左半部分和右半部分,依次复制到新数组中,并空出指定位置;
  3. 最后设置指定位置的元素,并指向新的数组。
    // add()方法的重载,在指定位置添加元素
    public void add(int index, E element) {
        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;
            // 如果要添加的位置在末尾,复制出一个长度+1的数组
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
	            // 将下标位置的左右两边拆为两个数组
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index); // 原数组的0位置开始,从新数组的0位置复制index个
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved); // 原数组的index位置开始,从新数组的index+1位置复制numMoved个
            }
            newElements[index] = element; // 设置指定位置的元素
            setArray(newElements); // 指向新的引用
        } finally {
            lock.unlock();
        }
    }

 remove(int index)

  • 判断要删除的元素是否为末尾元素,如果是,直接复制一个长度-1的数组;
  • 如果不是,复制一个长度-1的空数组,分别复制指定位置的左边和右边到新数组中;
  • 最后返回修改前该位置的值(oldValue)。
    // 删除指定位置的元素
    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)
                setArray(Arrays.copyOf(elements, len - 1)); // 复制长度-1的原数组,其他内容不变.并直接指向新的数组
            else {
                Object[] newElements = new Object[len - 1]; // 创建出一个长度减1的数组
                System.arraycopy(elements, 0, newElements, 0, index); // 复制指定元素左边的元素到新数组中,从0开始,复制index个
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved); // 复制指定元素右边的元素到新数组中
                setArray(newElements); // 指向新数组
            }
            return oldValue; // 返回删除前的元素
        } finally {
            lock.unlock();
        }
    }

removeRange(int fromIndex, int toIndex)

该方法删除指定范围内的元素:

  1. 创建一个新数组,长度是len - (toIndex - fromIndex);
  2. 将该区间的左右两边分别复制到新数组中;
  3. 指向新数组。
    // 删除指定范围内的元素,不包括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; //  计算删除区间的右边元素个数
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, newlen)); // 为零,表示删除最后一个元素,复制出除最后一个元素的原数组,并指向这个新数组
            else {
                Object[] newElements = new Object[newlen]; // 创建出新数组,长度为剩余元素个数
                System.arraycopy(elements, 0, newElements, 0, fromIndex); // 从原数组下标为0的位置复制fromIndex个到新数组中
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved); // 从删除区间的结束位置开始复制,从新数组的fromIndex(刚才复制了fromIndex个)位置复制numMoved个
                setArray(newElements); // 指向新的引用
            }
        } finally {
            lock.unlock();
        }
    }

removeAll(Collection<?> c)

删除指定集合与该集合相同的元素:

  1. 复制出一个与原数组等长的空数组;
  2. 遍历原数组,每次循环查看当前元素是否在指定集合中存在:
  3. 如果不存在,放入新的空数组temp中,并使temp数组的长度自增1;
  4. 遍历结束后,如果新的长度和原长度不相等,那么复制这个新数组,并指向它。
    // 从集合中移除与指定集合中c中相同的元素
    public boolean removeAll(Collection<?> c) {
        if (c == null) throw new NullPointerException(); // 如果传入的集合为null,抛出异常
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                Object[] temp = new Object[len]; // 复制出新的空数组,长度依然为原来的长度
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i]; // 拿出当前元素
                    if (!c.contains(element))
                        temp[newlen++] = element; // 如果集合c中不存在当前元素,则依次保存至temp数组中,并记录新数组的长度+1
                }
                if (newlen != len) {
                    setArray(Arrays.copyOf(temp, newlen));
                    return true; // 如果删掉了某些元素,那么按照修改后的长度复制一个新数组,并让它指向这个新数组,操作成功返回true
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

clear() 

清空集合

    // 清除所有元素
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock(); // 加锁
        try {
            setArray(new Object[0]); // 将其直接指向新的空数组(元素只有一个0,并不是为null)
        } finally {
            lock.unlock(); // 解锁
        }
    }

addAll(Collection<? extends E> c)

将指定集合中的元素添加到本集合中(末尾):

  1. 如果当前集合为空,直接指向指定集合的数组;
  2. 不为空,复制出新数组,长度为len + cs.length,将指定集合的数组复制到新数组的末尾。
    // 向当前集合中添加进指定集合中的元素
    public boolean addAll(Collection<? extends E> c) {
        Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
            ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();  // 判断指定集合是否为CopyOnWriteArrayList类型,如果是获取数组;不是则将指定集合转换为数组
        if (cs.length == 0)
            return false; // 如果添加的长度为0,无意义,返回false
        final ReentrantLock lock = this.lock;
        lock.lock(); // 上锁,确保线程安全
        try {
            Object[] elements = getArray(); // 获取本集合的数组
            int len = elements.length;
            // 如果当前集合为空,直接指向指定集合的数组形式
            if (len == 0 && cs.getClass() == Object[].class)
                setArray(cs);
            else {
                Object[] newElements = Arrays.copyOf(elements, len + cs.length); // 复制出新数组,长度为原数组长度+指定集合数组的长度
                System.arraycopy(cs, 0, newElements, len, cs.length); // 将指定集合中的元素复制到新数组的末尾
                setArray(newElements); // 指向新的引用
            }
            return true;
        } finally {
            lock.unlock();  // 释放锁
        }
    }

sort(Comparator<? super E> c)

传入一个比较器,按照比较器的规则进行排序,同样的,是在复制的数组里进行的,不改变原数组。

    // 对所有的元素进行排序
    public void sort(Comparator<? super E> c) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray(); // 获取原数组
            Object[] newElements = Arrays.copyOf(elements, elements.length); // 复制出新数组
            @SuppressWarnings("unchecked") E[] es = (E[])newElements; // 
            Arrays.sort(es, c); // 按照比较器c的规则进行排序
            setArray(newElements); // 将数组指向新的引用
        } finally {
            lock.unlock();
        }
    }

equals(Object o)

当前集合对象与指定对象进行比较:

  1. 获取指定对象的迭代器;
  2. 遍历当前集合对象并逐个比较每个元素是否相等,如果出现不一样的,返回false;
  3. 如果比较结束后指定对象还有剩余,显然两个对象不一样,返回false;
  4. 默认返回true。
    // 将本对象与指定对象进行比较
    public boolean equals(Object o) {
        if (o == this)
            return true; // 如果内存地址相同,那么一定相同,返回true
        if (!(o instanceof List)) // 如果传入的参数都不是List接口的实现类对象,那么一定不同,返回false
            return false;

        List<?> list = (List<?>)(o); // 类型转换
        Iterator<?> it = list.iterator(); // 获取对象o的迭代器
        Object[] elements = getArray(); // 得到原数组
        int len = elements.length;
        for (int i = 0; i < len; ++i)
            if (!it.hasNext() || !eq(elements[i], it.next()))
                return false; // 逐个比较两个对象的元素是否相等,如果有不相等的元素,则返回false
        if (it.hasNext())
            return false; // 如果比完后其他内容都相等,但是没有比完,返回false
        return true; // 默认返回true
    }

hashCode()

将每次计算的哈希值*31再加上每个元素的哈希值。

    // 计算哈希值
    public int hashCode() {
        int hashCode = 1;
        Object[] elements = getArray();
        int len = elements.length;
        for (int i = 0; i < len; ++i) {
            Object obj = elements[i];
            hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); // 将每次计算的哈希值*31再加上每个元素的哈希值
        }
        return hashCode;
    }
}

总结

CopyOnWriteArrayList具有以下特性:

  • 在保证并发读取的前提下,确保了写入时的线程安全
  • 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容
  • 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
  • CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性不能保证数据的实时一致性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值