CopyOnWriteArrayList

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

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

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

add(E e)方法:

添加新元素至集合时,会将当前数组Copy复制新数组,并将新元素添加至新数组,最后替换原数组。执行过程中,使用ReentrantLock加锁,保证线程安全,避免多个线程复制数组。

    // 添加元素
    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();// 最后在finally中释放锁
        }
    }

add(int index, E element)方法:

将元素添加到指定下标位置。

    // 将元素添加到指定位置
    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)// 判断指定下标是否越界,如果越界抛出IndexOutOfBountsException异常
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;// 定义一个新数组
            int numMoved = len - index;// 得到从该下标到数组末尾的长度
            if (numMoved == 0)// 当numMoved==0时,意味着要添加在数组末尾
                newElements = Arrays.copyOf(elements, len + 1);// 将当前数组长度加一复制到一个新数组中
            else {
                newElements = new Object[len + 1];// 将新数组的长度设置为当前数组长度加一
                System.arraycopy(elements, 0, newElements, 0, index);//利用Systems工具类中的方法将旧数组复制到新数组中
                System.arraycopy(elements, index, newElements, index + 1,//利用Systems工具类中的方法将旧数组复制到新数组中
                                 numMoved);
            }
            newElements[index] = element;// 将本次要添加的元素存到指定位置
            setArray(newElements);// 将新数组存回去
        } finally {
            lock.unlock();// 最后在finally中释放锁
        }
    }

小结:

从 add 系列方法可以看出,CopyOnWriteArrayList 通过加锁 + 数组拷贝+ volatile 来保证了线程安全,每一个要素都有着其独特的含义:

1.加锁:保证同一时刻数组只能被一个线程操作;
2.数组拷贝:保证数组的内存地址被修改,修改后触发 volatile 的可见性,其它线程可以立马知道数组已经被修改;
3.volatile:值被修改后,其它线程能够立马感知最新值。
三个个要素缺一不可,比如说我们只使用 1 和 3 ,去掉 2,这样当我们修改数组中某个值时,并不会触发 volatile 的可见特性的,只有当数组内存地址被修改后,才能触发把最新值通知给其他线程的特性。

remove(int index)方法:

删除指定下标元素。根据指定下标,从原数组中,Copy复制其它元素至新数组,最后替换原数组。

    // 通过指定下标删除元素
    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);// 通过get方法将数组和指定下标传进去返回值为要删除的元素
            int numMoved = len - index - 1;// 得到从该下标到数组末尾的长度
            if (numMoved == 0)// 当numMoved==0时,意味着要删除的元素在数组末尾
                setArray(Arrays.copyOf(elements, len - 1));// 将当前数组长度减一复制到一个新数组中
            else {
                Object[] newElements = new Object[len - 1];// 将新数组的长度设置为当前数组长度减一
                System.arraycopy(elements, 0, newElements, 0, index);//利用Systems工具类中的方法将旧数组复制到新数组中
                System.arraycopy(elements, index + 1, newElements, index,//利用Systems工具类中的方法将旧数组复制到新数组中
                                 numMoved);
                setArray(newElements);// 将新数组存回去
            }
            return oldValue;// 返回要删除的元素
        } finally {
            lock.unlock();// 最后在finally中释放锁
        }
    }

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();// 如果越界抛出IndexOutOfBoundsException异常
            int newlen = len - (toIndex - fromIndex);// 得到删除后新数组的长度
            int numMoved = len - toIndex;// 得到从该下标到数组末尾的长度
            if (numMoved == 0) // 如果numMoved == 0意味着原数组全部都会删除
                setArray(Arrays.copyOf(elements, newlen)); 
            else {
                Object[] newElements = new Object[newlen]; // 创建一个新数组,指定长度为newlen
                System.arraycopy(elements, 0, newElements, 0, fromIndex);//利用Systems工具类中的方法将旧数组复制到新数组中
                System.arraycopy(elements, toIndex, newElements,//利用Systems工具类中的方法将旧数组复制到新数组中
                                 fromIndex, numMoved);
                setArray(newElements);// 将新数组存回去
            }
        } finally {
            lock.unlock();// 最后在finally中释放锁
        }
    }

removeAll(Collection<?> c)方法:

批量删除包含在 c 中的元素

    // 批量删除包含在 c 中的元素
	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;
			// 说明数组有值,数组无值直接返回 false
			if (len != 0) {
				// newlen 表示新数组的索引位置,新数组中存在不包含在 c 中的元素
				int newlen = 0;
				Object[] temp = new Object[len];
				// 循环,把不包含在 c 里面的元素,放到新数组中
				for (int i = 0; i < len; ++i) {
					Object element = elements[i];
					// 不包含在 c 中的元素,从 0 开始放到新数组中
					if (!c.contains(element))
						temp[newlen++] = element;
				}
				// 拷贝新数组,变相的删除了不包含在 c 中的元素
				if (newlen != len) {
					setArray(Arrays.copyOf(temp, newlen));
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值