概述

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

定义

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

属性

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

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

初始化

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

/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}

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

/**
* Creates a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection of initially held elements
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}

方法

add(E e)

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 锁 1.5 版本的锁 已经不用synchronizated
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();
}
}

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

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

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

/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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];
}

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

总结

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

JDK源码分析(10) CopyOnWriteArrayList的更多相关文章

  1. 【JDK】JDK源码分析-Vector

    概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...

  2. 【JDK】JDK源码分析-ArrayList

    概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...

  3. 【JDK】JDK源码分析-CountDownLatch

    概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...

  4. Solr4.8.0源码分析(10)之Lucene的索引文件(3)

    Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这 ...

  5. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  6. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  7. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  8. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  9. JDK源码分析(2)LinkedList

    JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...

  10. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

随机推荐

  1. dede添加自定义函数

    在dede安装目录下的include/extend.func.php添加自定义函数: /** * 获取文章第一张图片 */ function getFirstImg($arcId) { global ...

  2. DB2频繁出现死锁,常用解决问题的命令

    --DB2频繁出现死锁,常用解决问题的命令db2 get snapshot for locks on sampledb2 get db cfg for sampledb2 update db cfg ...

  3. This page contains the following error

    解决办法:将header头注释掉 header("content-type:text/xml; charset=UTF-8");

  4. Eloquent Observer 的小坑

    前言 最近开发新的项目不是基于完整的 laravel 框架,框架是基于 laravel ORM 的简单MVC模式.随着项目的成熟和业务需求,需要引入事件机制.简单地浏览了一下 symfony.lara ...

  5. 解决Android启动显示空白界面的问题

    Android程序启动时,第一个看的界面并不是我们的指定的第一个Activity界面,而是显示了一个空白的界面,带标题栏的,但是界面什么内容都没有,这个界面只显示不到1秒左右的时间就会切换到我们的第一 ...

  6. 使用bat批处理文件备份postgresql数据库

    @echo offset pgsql_path=d:\"Program Files"\PostgreSQL\9.3\bin\   //安装目录set database=postgr ...

  7. Docker mysql启动自动按顺序导入sql

    1.目录结构 -rw-r--r-- root root Jan : Dockerfile -rw-r--r-- root root Jan : initdb.sh drwxr-xr-x root ro ...

  8. Android 6.0以上 需要运行时申请的权限

    转载:http://www.cnblogs.com/tangs/articles/6377347.html 自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifes ...

  9. PS各个工具的字母快捷键和英文全名

    选框-Marquee(M) 移动-move(V) 套索-Lasso(L) 魔棒-Wand(W) 喷枪-injection lance (J) 画笔-Brush (B) 铅笔-pencil(N) 橡皮图 ...

  10. 百度上传插件(webupload)单文件(单图片)上传设置

    var uploader = WebUploader.create({                 //auto : true,                 swf : '${ctx}/sta ...