Java集合干货——CopyOnWriteArrayList源码分析

栏目: Java · 发布时间: 6年前

内容简介:Java集合干货——CopyOnWriteArrayList源码分析

CopyOnWriteArrayList是一个线程安全集合,原理简单说就是:在保证线程安全的前提下,牺牲掉写操作的效率来保证读操作的高效。所谓CopyOnWrite就是通过复制的方式来完成对数据的修改,在进行修改的时候,复制一个新数组,在新数组上面进行修改操作,这样就保证了不改变老数组,也就没有一写多读数据不一致的问题了。

具体的实现来看源码,JDK 8。

CopyOnWriteArrayList

定义

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

在定义上和ArrayList大差不差,不过多解释,有兴趣可以看之前关于ArrayList的文章。

属性

一个是Lock,另一个是一个对象数组。

/** The lock protecting all mutators */
//一把锁
transient final ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
//一个对象数组,只从方法getArray/setArray处接受值
//volatile后面会有专门的文章来说明
private volatile transient Object[] array;

初始化

CopyOnWriteArrayList的初始化容量是0,分为这样的几个步骤。

//在无参构造方法中会调用setArray方法,参数是一个空的对象数组,然后通过setArray把这个空的数组赋值给属性array
public CopyOnWriteArrayList() {
  setArray(new Object[0]);
}
final void setArray(Object[] a) {
  array = a;
}

需要说明的是另一个有参构造方法,参数可以是一个集合

//按照集合的迭代器返回的顺序创建一个包含指定集合元素的列表
public CopyOnWriteArrayList(Collection<? extends E> c) {
  //将集合转为数组
  Object[] elements = c.toArray();
//elements不能够是一个空的对象数组 为什么要if这样一个条件嘞  因为属性中需要赋值的是一个对象数组  所以如果if成立执行的就是把原数组变为一个对象数组  如果本身就是对象数组也就不用转了
  if (elements.getClass() != Object[].class)
    elements = Arrays.copyOf(elements, elements.length, Object[].class);
  //赋值给属性
  setArray(elements);
}

方法

add(E e)

添加一个新元素到list的尾部。

public boolean add(E e) {
  //锁 1.5新版本的锁 已经不用synchronized了
  final ReentrantLock lock = this.lock;
  //加锁
  lock.lock();
  try {
    //getArray获取属性值 就是老数组
    Object[] elements = getArray();
    int len = elements.length;
    //这里是重点 在这里 复制老数组得到了一个长度+1的新数组
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    //添加元素
    newElements[len] = e;
    //用新数组取代老数组
    setArray(newElements);
    return true;
  } finally {
    lock.unlock();
  }
}

从add方法中我们可以看到所谓的CopyOnWrite是如何实现的,在需要修改的时候,复制一个新数组,在新数组上修改,修改结束取代老数组,这样保证了修改操作不影响老数组的正常读取,另修改操作是加锁的,也就是说没有了线程不安全的问题。

和ArrayList相比较,效率比较低,只添加一个元素的情况下(初始容量均为0),用时是ArrayList的5倍左右,但是随着CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代价将越来越昂贵。

除了添加其他的修改操作也都是这样的套路,不做过多解释,如remove,也是加锁,复制新数组。

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

#####get

public E get(int index) {
  return get(getArray(), index);
}
//按照下标获取数组中对应的元素
private E get(Object[] a, int index) {
  return (E) a[index];
}

读取的方法就很简单了,按照下标获取对应的元素。

CopyOnWriteArrayList总结

  1. 读写分离,我们修改的是新数组,读取的是老数组,不是一个对象,实现了读写分离。这种技术数据库用的非常多,在高并发下为了缓解数据库的压力,即使做了缓存也要对数据库做读写分离,读的时候使用读库,写的时候使用写库,然后读库、写库之间进行一定的同步,这样就避免同一个库上读、写的IO操作太多。
  2. 场景:读操作远多于修改操作

我不能保证每一个地方都是对的,但是可以保证每一句话,每一行代码都是经过推敲和斟酌的。希望每一篇文章背后都是自己追求纯粹技术人生的态度。

永远相信美好的事情即将发生。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

浪潮式发售

浪潮式发售

[美] 杰夫.沃克(Jeff Walker) / 李文远 / 广东人民出版社 / 2016-3-1 / 39.80元

10天时间,4种发售路径, 让你的产品一上架就被秒杀 投资失败的个体户,怎样让长期积压的库存,变成众人抢购的稀缺品,最终敲开财富之门? 只有一腔热血的大学毕业生,怎样将原本无人问津的网球课程,发售成价值45万美元的专业教程? 长期脱离社会的全职主妇,如何白手起家,创造出自己的第一款爆品,并挽救即将破碎的家庭? 改变上述人士命运的是同一件法宝——产品发售方程式。互......一起来看看 《浪潮式发售》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试