CopyOnWriteArrayList与CopyOnWriteArraySet详解(copyonwritearraylist原理)

  本篇文章为你整理了CopyOnWriteArrayList与CopyOnWriteArraySet详解(copyonwritearraylist原理)的详细内容,包含有copyonwritearraylist缺点 copyonwritearraylist原理 copyonwritearraylist remove copyonwritearraylist性能 CopyOnWriteArrayList与CopyOnWriteArraySet详解,希望能帮助你了解 CopyOnWriteArrayList与CopyOnWriteArraySet详解。

  什么是CopyOnWrite容器

   【1】CopyOnWrite容器是基于并发模式Copy-on-Write模式(最简单的并发解决方案)实现的用于避免共享的数据集合。
 

   【2】CopyOnWrite容器又被成为写时复制的容器,即当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

   【3】适用场景:读多写少的场景。
 

  

  源码分析CopyOnWriteArrayList的实现

   【1】属性说明

  

//用于锁住所有变化情况

 

  final transient ReentrantLock lock = new ReentrantLock();

  //存储数据的数组只能通过getArray/setArray进行改变

  private transient volatile Object[] array;

 

  

   【2】方法解析(仅展示部分方法)

   1)添加方法

  

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

  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;

   if (numMoved == 0)

   newElements = Arrays.copyOf(elements, len + 1);

   else {

   newElements = new Object[len + 1];

   System.arraycopy(elements, 0, newElements, 0, index);

   System.arraycopy(elements, index, newElements, index + 1, numMoved);

   newElements[index] = element;

   setArray(newElements);

   } finally {

   lock.unlock();

  }

 

   2)设置方法

  

public E set(int index, E element) {

 

   final ReentrantLock lock = this.lock;

   lock.lock();

   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 {

   // 这里其实是将副本,重新放回去

   setArray(elements);

   return oldValue;

   } finally {

   lock.unlock();

  }

 

  

   3)删除方法

  

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

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

  }

 

  

   4)获取方法

  

private E get(Object[] a, int index) {

 

   return (E) a[index];

  public E get(int index) {

   return get(getArray(), index);

  //final修饰方法之后该方法无法被子类覆盖

  final Object[] getArray() {

   return array;

  }

 

  

   【3】汇总说明

   1.CopyOnWriteArrayList之所以选择数组而不是链表作为变量的存储空间的原因:

   1)提高处理速度,因为数组存储在内存中一块连续的空间,而链表则是分散的,采用Arrays.copyOf 本质上底层还是使用 System.arraycopy 将那块连续的内存空间的数据一次性拷贝,减少操作次数。

   2.由源码可以看到,每次进行修改的时候都会加锁仅限于一个线程进行变更操作,避免了共享变量并发写的问题。所以是线程安全的。

   3.但是其占用内存空间容易出现问题,如:在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。而Full GC过长则应用响应时间也随之变长。

   4.数据一致性问题,我们可以看出数据并不是实时一致性的,而是最终一致性。因为会先将数据拷贝到newElements 中,再设置到array的指针指向。要知道操作系统是基于时间片轮转机制分配运行时间(如:时间耗尽没有新的时间片给予,会导致线程上下文切换),所以中间的间隔时间可以假设很长,那么修改是写入了,但是变更还没进行。其次,在加锁的时间内,其他线程读取的其实都是没有修改的数据。

  

  源码分析CopyOnWriteArraySet的实现

   【1】属性说明

  

private final CopyOnWriteArrayList E 

 

   【2】方法说明

  

public boolean add(E e) {

 

   return al.addIfAbsent(e);

  //CopyOnWriteArrayList类的方法

  public boolean addIfAbsent(E e) {

   Object[] snapshot = getArray();

   return indexOf(e, snapshot, 0, snapshot.length) = 0 ? false : addIfAbsent(e, snapshot);

  private static int indexOf(Object o, Object[] elements, int index, int fence) {

   if (o == null) {

   for (int i = index; i fence; i++)

   if (elements[i] == null)

   return i;

   } else {

   for (int i = index; i fence; i++)

   if (o.equals(elements[i]))

   return i;

   return -1;

  private boolean addIfAbsent(E e, Object[] snapshot) {

   final ReentrantLock lock = this.lock;

   lock.lock();

   try {

   Object[] current = getArray();

   int len = current.length;

   if (snapshot != current) {

   // Optimize for lost race to another addXXX operation

   int common = Math.min(snapshot.length, len);

   for (int i = 0; i common; i++)

   if (current[i] != snapshot[i] eq(e, current[i]))

   return false;

   if (indexOf(e, current, common, len) = 0)

   return false;

   Object[] newElements = Arrays.copyOf(current, len + 1);

   newElements[len] = e;

   setArray(newElements);

   return true;

   } finally {

   lock.unlock();

  }

 

  

   【3】汇总说明

   1.CopyOnWriteArraySet的实现严格来说是基于CopyOnWriteArrayList进行实现的,去重逻辑在add中体现。

   2.其次是效率问题:每次插入都需要去遍历CopyOnWriteArrayList数组一次。

   3.虽然也是线程安全的,但是CopyOnWriteArrayList的缺点全部都会继承。

  

  以上就是CopyOnWriteArrayList与CopyOnWriteArraySet详解(copyonwritearraylist原理)的详细内容,想要了解更多 CopyOnWriteArrayList与CopyOnWriteArraySet详解的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: