java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
这篇文章主要为大家详细介绍了 java 并发容器 CopyOnWriteArrayList 实现原理及源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
CopyOnWriteArrayList 是 Java 并发包中提供的一个并发容器,它是个线程安全且读操作无锁的 ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为 "写时复制器",Java 并发包中类似的容器还有 CopyOnWriteSet。本文会对 CopyOnWriteArrayList 的实现原理及源码进行分析。
实现原理
我们都知道,集合框架中的 ArrayList 是非线程安全的,Vector 虽是线程安全的,但由于简单粗暴的锁同步机制,性能较差。而 CopyOnWriteArrayList 则提供了另一种不同的并发处理策略(当然是针对特定的并发场景)。
很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList 容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
优缺点分析
了解了 CopyOnWriteArrayList 的实现原理,分析它的优缺点及使用场景就很容易了。
优点:
读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java 的 list 在遍历时,若中途有别的线程对 list 容器进行修改,则会抛出 ConcurrentModificationException 异常。而 CopyOnWriteArrayList 由于其 "读写分离" 的思想,遍历和修改操作分别作用在不同的 list 容器,所以在使用迭代器进行遍历时候,也就不会抛出 ConcurrentModificationException 异常了
缺点:
缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁 GC;二是无法保证实时性,Vector 对于读写操作均加锁同步,可以保证读和写的强一致性。而 CopyOnWriteArrayList 由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。
源码分析
基本原理了解了,CopyOnWriteArrayList 的代码实现看起来就很容易理解了。
- public boolean add(E e) {
- //ReentrantLock加锁,保证线程安全
- 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();
- }
- }
添加的逻辑很简单,先将原容器 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);
- int numMoved = len - index - 1;
- if (numMoved == 0)
- //如果要删除的是列表末端数据,拷贝前len-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();
- }
- }
删除操作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写操作,需要加锁。
我们再来看看读操作,CopyOnWriteArrayList 的读操作是不用加锁的,性能很高。
- public E get(int index) {
- return get(getArray(), index);
- }
- 直接读取即可,无需加锁
- private E get(Object[] a, int index) {
- return (E) a[index];
- }
总结
本文对 CopyOnWriteArrayList 的实现原理和源码进行了分析,并对 CopyOnWriteArrayList 的优缺点也进行了分析(Java 并发包中还提供了 CopyOnWriteSet,原理类似)。其实所谓并发容器的优缺点,无非是取决于我们在面对特定并发场景时,是否能做出相对合理的选择和应用。也希望本文能帮助到有需要的童鞋,共勉。
来源: http://www.phperz.com/article/17/1227/356908.html