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. 场景:读操作远多于修改操作

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

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


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

查看所有标签

猜你喜欢:

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

Learning PHP, MySQL, and JavaScript

Learning PHP, MySQL, and JavaScript

Robin Nixon / O'Reilly Media / 2009-7-21 / USD 39.99

Learn how to create responsive, data-driven websites with PHP, MySQL, and JavaScript - whether or not you know how to program. This simple, streamlined guide explains how the powerful combination of P......一起来看看 《Learning PHP, MySQL, and JavaScript》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具