CopyOnWriteArrayList源码分析

目录

一、基本思想

 二、底层源码

1、构造方法

CopyOnWriteArrayList()

 CopyOnWriteArrayList(Collection c)

  2、修改元素---set()

 3、添加元素--add()

 4、删除元素--remove()

 (1) 删除指定位置上的元素

 (2)删除指定区间的元素

 5、指定位置添加集合--addAll()

 6、保留指定集合中的元素--retainAll()

 7、清空--clear()


一、基本思想

     CopyOnWriteArrayList继承List集合,是一种集合并发访问的类。当对集合进行修改,添加,删除时,首先会拷贝一个新的集合进行操作,操作完成后,会将原集合的引用指向新的集合。保证了集合写入时的线程安全,并且多个线程可以进行并发的读取操作。

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

 二、底层源码

      CopyOnWriteArrayList类是线程安全的,使用ReentrantLock加锁,底层数组采用Object类型的数组,该数组是线程可见的,使用volatile修饰。

    // 加锁(可重入锁)
	final transient ReentrantLock lock = new ReentrantLock();
	// 设置为线程可见数组
	private transient volatile Object[] array;

1、构造方法

CopyOnWriteArrayList()

     该无参构造方法初始化为长度为0的一个数组

	// 无参构造
	public CopyOnWriteArrayList() {
		// 创建长度为0的数组
		setArray(new Object[0]);
	}
	final void setArray(Object[] a) {
		// 将修改后的数组赋给“volatile”数组
		array = a;
	}

 CopyOnWriteArrayList(Collection<? extends E> c)

   将集合中的元素复制到数组中

// 有参构造
	// 参数:集合
	// 将指定集合中的元素copy到数组中
	public CopyOnWriteArrayList(Collection<? extends E> c) {
		Object[] elements;
		// 如果c也是CopyOnWriteArrayList类型
		// 那么直接把它的数组拿过来使用
		if (c.getClass() == CopyOnWriteArrayList.class)
			elements = ((CopyOnWriteArrayList<?>) c).getArray();
		// 如果不是CopyOnWriteArrayList类型
		else {
			// 将集合转为数组
			elements = c.toArray();
			// 判断elements是不是Object[]类型
			if (elements.getClass() != Object[].class)
				// 如果不是,重新拷贝成Object[].class类型的数组
				elements = Arrays.copyOf(elements, elements.length, Object[].class);
		}
		setArray(elements);
	}

  2、修改元素---set()

    当修改数组的某个元素时,会将当前数组Copy复制新数组,在新数组中修改完后,最后替换原数组。整个过程加锁,保证线程的安全

// 修改指定下标的值
	// 参数:指定下标:index 修改的值:element
	public E set(int index, E element) {
		// 加锁,保证在修改的时候,其他线程只允许读
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 获取“volatile”数组
			Object[] elements = getArray();
			// 获取该数组index下标上原来的值
			E oldValue = get(elements, index);
			// 判断原值是否与新值相同
			// 如果不同
			if (oldValue != element) {
				// 获取原数组的长度
				int len = elements.length;
				// 拷贝出Object[].class类型的新数组
				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();
		}
	}

3、添加元素--add()

    (1)添加元素至集合末尾

   (2)添加元素至集合指定位置

     给集合添加元素,先拷贝一个新集合,在新集合上添加,最后替换原数组

     以上操作都是在加锁的情况下进行,保证了线程安全

// 添加值
	public boolean add(E e) {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			Object[] elements = getArray();
			int len = elements.length;
			// 拷贝"volatile”数组并且将长度加1
			Object[] newElements = Arrays.copyOf(elements, len + 1);
			// 将新元素赋给新数组
			newElements[len] = e;

			// 将新数组赋给原数组
			setArray(newElements);
			return true;
		} finally {
			lock.unlock();
		}
	}

	// 添加元素element到指定的下标index
	// index:指定添加的下标
	// element:新值
	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新数组
				newElements = Arrays.copyOf(elements, len + 1);
			else {
				// 不在末尾
				// 创建一个len+1的数组
				newElements = new Object[len + 1];
				// 将新值的前半部分赋给新数组
				System.arraycopy(elements, 0, newElements, 0, index);
				// 将新值的后半部分赋值给后半部分
				System.arraycopy(elements, index, newElements, index + 1, numMoved);
			}
			// 将新值赋给index下标位置
			newElements[index] = element;
			setArray(newElements);
		} finally {
			lock.unlock();
		}
	}

4、删除元素--remove()

 (1) 删除指定位置上的元素

 (2)删除指定区间的元素

删除集合中元素,首先拷贝一个新数组,判断删除的元素是否在末尾

   若是末尾直接替换为原数组

         若单个元素,数组长度加1;若为多个元素,数组长度减相应的长度。

    若不是末尾元素则将分段拷贝成新数组

最终将替换为原数组

 以上操作都是在加锁的情况下进行,保证了线程安全

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);
			// 获取后半部分元素个数-1
			int numMoved = len - index - 1;
			// 删除的元素恰好在末尾
			if (numMoved == 0)
				// 拷贝数组(长度减1)赋给原数组
				setArray(Arrays.copyOf(elements, len - 1));
			else {
				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();
		}
	}

	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);

			// 计算toIndex下标后的元素个数
			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);
				System.arraycopy(elements, toIndex, newElements, fromIndex, numMoved);
				setArray(newElements);
			}
		} finally {
			lock.unlock();
		}
	}

5、指定位置添加集合--addAll()

给集合在指定位置添加集合

   若是末尾直接替换为原数组(数组长度+添加集合长度)

   若不是末尾元素则将分段拷贝成新数组,最终将替换为原数组

   以上操作都是在加锁的情况下进行,保证了线程安全

// 在指定下标添加一个集合
	public boolean addAll(int index, Collection<? extends E> c) {

		// 将集合转换为object数组
		Object[] cs = c.toArray();
		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);
			// 如果集合元素为0
			if (cs.length == 0)
				return false;
			// index后的长度
			int numMoved = len - index;
			Object[] newElements;

			// 末尾
			if (numMoved == 0)
				// copy一个数组(数组长度len+集合长度)
				newElements = Arrays.copyOf(elements, len + cs.length);
			else {
				// 创建一个数组(数组长度len+集合长度)
				newElements = new Object[len + cs.length];
				// 将源数组中index前赋值为newElements数组
				System.arraycopy(elements, 0, newElements, 0, index);
				// 将源数组中【index + cs.length,len】前赋值为newElements数组
				System.arraycopy(elements, index, newElements, index + cs.length, numMoved);
			}
			// 将cs集合赋值给新数组【index,index + cs.length)
			System.arraycopy(cs, 0, newElements, index, cs.length);
			setArray(newElements);
			return true;
		} finally {
			lock.unlock();
		}
	}

6、保留指定集合中的元素--retainAll()

  创建一个等长的数组,遍历等长数组,依次判断集合中是否包含,若包含则添加到新数组中,最终替换为原数组。 以上操作都是在加锁的情况下进行,保证了线程安全

	// 保留此列表中包含在指定集合中的元素
	public boolean retainAll(Collection<?> c) {
		// 如果传入集合为null
		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数组下标的自增
				int newlen = 0;

				// 创建一个和原数组一样长的数组
				Object[] temp = new Object[len];

				// 遍历源数组
				for (int i = 0; i < len; ++i) {
					// 获取当前下标下的值
					Object element = elements[i];
					// 判断集合是否包含当前下标的值
					if (c.contains(element))
						// 若包含
						// 将值放入temp数组中
						temp[newlen++] = element;
				}
				// 如果赋值后temp数组长度不等于源数组长度
				if (newlen != len) {
					// 拷贝数组(长度为newLen)赋给原数组
					setArray(Arrays.copyOf(temp, newlen));
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

7、清空--clear()

初始化为长度0的数组,该过程加锁,保证线程的安全

public void clear() {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 将源数组设置为元素个数为0的数组
			setArray(new Object[0]);
		} finally {
			lock.unlock();
		}
	}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值