CopyOnWriteArrayList核心源码阅读

CopyOnWriteArrayList

        针对List、Map、Set、Deque等,java.util.concurrent包也提供了对应的并发集合类。例如CopyOnWriteArrayList。

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

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

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

下面我们对部分源码进行分析:

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

	// 写入操作时竞争的锁对象
	final transient ReentrantLock lock = new ReentrantLock();

	// 基于数组实现的,只能通过getArray()和setArray()来读写
	private transient volatile Object[] array;

	/**
	 * Gets the array. Non-private so as to also be accessible from
	 * CopyOnWriteArraySet class.
	 */
	final Object[] getArray() {
		return array;
	}

	/**
	 * Sets the array.
	 */
	final void setArray(Object[] a) {
		array = a;
	}

	// 无参的构造方法,调用的时候内部创建一个大小为0的空数组
	public CopyOnWriteArrayList() {
		setArray(new Object[0]);
	}

	// 私有的get方法,用来返回当前数组a下标为index的元素
	@SuppressWarnings("unchecked")
	private E get(Object[] a, int index) {
		return (E) a[index];
	}

	// 通过指定的下标获取指定的元素,调用get(Object[] a,int index)方法返回元素
	public E get(int index) {
		return get(getArray(), index);
	}

	// 用来给指定下标设置值,同时返回旧值,是一个写入操作,需要竞争到锁才能使用
	public E set(int index, E element) {
		final ReentrantLock lock = this.lock;
		lock.lock();// 加锁
		try {
			// 获取当前存储元素的数组
			Object[] elements = getArray();
			// 获取当前下标对应的旧元素
			E oldValue = get(elements, index);
			/*
			 * 判断:如果旧元素不等于新元素: 拷贝一个一样的数组,替换下标元素,然后写入array
			 */
			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
				// 即时元素没有变化,也要写入array
				setArray(elements);
			}
			// 返回旧的元素
			return oldValue;
		} finally {
			// 释放锁
			lock.unlock();
		}
	}

	// 向集合中添加一个元素。同样是写入操作,需要竞争锁
	public boolean add(E e) {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 先获取本来的数组array
			Object[] elements = getArray();
			int len = elements.length;
			// 然后拷贝一个长度加一的数组,因为每次只能加入一个元素
			Object[] newElements = Arrays.copyOf(elements, len + 1);
			// 将需要加入的元素放在新数组的末尾
			newElements[len] = e;
			// 写入array 覆盖原数组
			setArray(newElements);
			return true;
		} finally {
			lock.unlock();
		}
	}

	// 通过指定下标添加元素,需要竞争锁
	public void add(int index, E element) {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 获取原数组
			Object[] elements = getArray();
			int len = elements.length;
			// 判断index是否合法
			if (index > len || index < 0)
				throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + len);
			// 新数组
			Object[] newElements;
			// 得到该下标距离末尾的长度
			int numMoved = len - index;
			if (numMoved == 0) // 如果等于0,意味着要添加在数组的末尾
				// 将当前数组长度加一复制到一个新数组中,前面的元素与本来的数组相同
				newElements = Arrays.copyOf(elements, len + 1);
			else {
				// 如果numMoved不为0,那么将新数组的长度设置为当前数组长度加一
				newElements = new Object[len + 1];
				// 拷贝旧数组从下标0到index-1的元素到新数组中
				System.arraycopy(elements, 0, newElements, 0, index);
				// 拷贝旧数组的其他元素到新数组下标为index+1到末尾,空出index的位置
				System.arraycopy(elements, index, newElements, index + 1, numMoved);
			}
			// 将新元素加入新数组中
			newElements[index] = element;
			// 写入array,覆盖原数组
			setArray(newElements);
		} finally {
			lock.unlock();
		}
	}

	// 通过指定下标删除集合元素 需要竞争锁
	public E remove(int index) {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 获取原数组
			Object[] elements = getArray();
			int len = elements.length;
			// 通过get方法获取指定下标对应的旧元素
			E oldValue = get(elements, index);
			// 计算该下标距离数组末尾的长度
			int numMoved = len - index - 1;
			if (numMoved == 0)
				// numMoved为0的时候,意味着需要删除的元素就是最后一个元素
				// 将当前数组长度减一直接写入原数组array,覆盖
				setArray(Arrays.copyOf(elements, len - 1));
			else {
				// numMoved不为0的时候,创建一个长度减一的新数组
				Object[] newElements = new Object[len - 1];
				// 拷贝原数组从下标0到index-1的元素到新数组
				System.arraycopy(elements, 0, newElements, 0, index);
				// 然后将原数组从index+1到结束复制到新数组的index下标位置,将原数组的index下标移除
				System.arraycopy(elements, index + 1, newElements, index, numMoved);
				// 然后覆盖原数组array
				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);
			// 计算下标区间右值距离原数组末尾的长度
			int numMoved = len - toIndex;
			if (numMoved == 0)
				// 如果numMoved为0,直接复制原数组从头复制到newlen位置
				setArray(Arrays.copyOf(elements, newlen));
			else {
				// 如果numMoved不为0,创建一个新的数组,长度为newlen
				Object[] newElements = new Object[newlen];
				// 拷贝原数组从下标0到fromInde位置的元素到新数组
				System.arraycopy(elements, 0, newElements, 0, fromIndex);
				// 拷贝原数组从下标toIndex到末尾的元素到新数组下标fromIndex到末尾
				System.arraycopy(elements, toIndex, newElements, fromIndex, numMoved);
				// 覆盖原数组
				setArray(newElements);
			}
		} finally {
			lock.unlock();
		}
	}

	// 删除指定集合中包含的所有元素
	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
				// 不为0的时候,说明数组有值,数组无值直接返回 false
				// 创建一个空数组temp,长度为len,用来暂时存放元素
				int newlen = 0;// 表示新数组的索引位置
				Object[] temp = new Object[len];
				// 遍历原数组,获取每一个元素,将不包含在c中的元素放入新数组中
				for (int i = 0; i < len; ++i) {
					Object element = elements[i];
					// 判断集合中是否存在该元素
					if (!c.contains(element))
						// 将c中不存在的元素加入空数组temp中
						temp[newlen++] = element;
				}
				// 判断空函数的长度是否等于原数组
				if (newlen != len) {
					// 如果不等于,拷贝新数组,变相的删除了不包含在 c 中的元素
					setArray(Arrays.copyOf(temp, newlen));
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

	// 清空数组 需要竞争锁
	public void clear() {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// 给原数组覆盖一个长度为0的数组,清空原数组的内容
			setArray(new Object[0]);
		} finally {
			lock.unlock();
		}
	}

	// 添加批量元素
	public boolean addAll(Collection<? extends E> c) {
		// 将需要加入的集合转换为数组
		Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? ((CopyOnWriteArrayList<?>) c).getArray()
				: c.toArray();
		// 如果长度为0,直接返回false
		if (cs.length == 0)
			return false;
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			Object[] elements = getArray();
			int len = elements.length;
			// 如果原数组为空并且类型为Object,则直接将新数组覆盖给原数组
			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();
		}
	}

	// 对集合内的元素进行排序,需要竞争锁 实现Comparator接口,定义排序规则
	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);
			// 覆盖原数组
			setArray(newElements);
		} finally {
			lock.unlock();
		}
	}

	// 比较指定对象与此列表的相等性
	public boolean equals(Object o) {
		// 先比较内存地址 如果相等直接返回true
		if (o == this)
			return true;
		// 判断该对象的类型是否属于List,否则直接返回false
		if (!(o instanceof List))
			return false;

		// 将该对象向上转型为List
		List<?> list = (List<?>) (o);
		// 获取list的迭代器
		Iterator<?> it = list.iterator();
		// 获取原数组
		Object[] elements = getArray();
		int len = elements.length;
		// 遍历原数组 判断元素是否相等,不相等或者该对象元素不够则直接返回false
		for (int i = 0; i < len; ++i)
			if (!it.hasNext() || !eq(elements[i], it.next()))
				return false;
		// 遍历结束后该对象仍然有元素 直接返回false
		if (it.hasNext())
			return false;
		return true;
	}

	// 获取该集合的哈希值
	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());
		}
		return hashCode;
	}
}

以上是对部分源代码的解析,因为源代码过多,所以只对关键部分进行解析

CopyOnWriteArrayList的特性:

1、在保证并发读取的前提下,确保了写入时的线程安全;

2、由于每次写入操作时,进行了Copy复制原数组,所以无序扩容;

3、适合读少写多的应用场景。由于add()、set()、remove()等修改操作需要复制整个数组,所以会有内存开销大的问题;

4、CopyOnWriteArrayList由于只在写入的适合加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农叮叮车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值