Java并发-CopyOnWriteArrayList

GitMaster
• 阅读 156

前言
今天我们一起学习下java.util.concurrent并发包里的CopyOnWriteArrayList工具类。当有多个线程可能同时遍历、修改某个公共数组时候,如果不希望因使用synchronize关键字锁住整个数组而影响性能,可以考虑使用CopyOnWriteArrayList。
CopyOnWriteArrayList API
CopyOnWriteArrayList的定义如下:
public class CopyOnWriteArrayList
extends Object
implements List, RandomAccess, Cloneable, Serializable
它也属于Java集合框架的一部分,是ArrayList的线程安全的变体,跟ArrayList的不同在于:CopyOnWriteArrayList针对数组的修改操作(add、set等)是基于内部拷贝的一份数据而进行的。换句话说,即使在一个线程进行遍历操作时有其他线程可能进行插入或删除操作,我们也可以“线程安全”得遍历CopyOnWriteArrayList。
例子1:插入(删除)数据的同时进行遍历
CopyOnWriteArrayList的实现原理是,在一个线程开始遍历(创建Iterator对象)时,内部会创建一个“快照”数组,遍历基于这个快照Iterator进行,在遍历过程中这个快照数组不会改变,也就不会抛出ConcurrentModificationException。如果在遍历的过程中有其他线程尝试改变数组的内容,就会拷贝一份新的数据进行变更,而后面再来访问这个数组的线程,看到的就是变更过的数组。
创建一个CopyOnWriteArrayList数组numbers;
CopyOnWriteArrayList numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});

创建一个遍历器iterator;
Iterator iterator = numbers.iterator();

给numbers中增加(或删除、修改)一个元素;
numbers.add(100);

利用iterator遍历数组的元素,发现遍历的结果是Iterator对象创建之前的;
List result = new LinkedList<>();
iterator.forEachRemaining(result::add);
assertThat(result).containsOnly(1, 3, 5, 78);
完整的例子如下:
package org.java.learn.concurrent.copyonwritearraylist;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.assertj.core.api.Assertions.*;
/**
作用:
User: duqi
Date: 2017/11/9
Time: 11:20
*/

public class CopyOnWriteArrayListExample {
[Java] 纯文本查看 复制代码
?

public static void main(String[] args) {

CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});

Iterator<Integer> iterator = numbers.iterator();
numbers.add(100);
List<Integer> result = new LinkedList<>();
iterator.forEachRemaining(result::add);
assertThat(result).containsOnly(1, 3, 5, 78);

Iterator<Integer> iterator2 = numbers.iterator();
numbers.remove(3);
List<Integer> result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);
assertThat(result2).containsOnly(1, 3, 5, 78, 100);

}
}
例子2:不支持一边遍历一边删除
由于CopyOnWriteArrayList的实现机制——>修改操作和读操作拿到的Iterator对象指向的不是一个数组,因此不支持基于Iterator对象的方法结果的删除:public void remove();,例子代码如下:
package org.java.learn.concurrent.copyonwritearraylist;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
作用: User: duqi Date: 2017/11/9 Time: 13:40
*/

public class CopyOnWriteArrayListExample2 {
[Java] 纯文本查看 复制代码
?

public static void main(String[] args) {

try {
    testExceptionThrow();
} catch (Exception e) {
    e.printStackTrace();
}

}

private static void testExceptionThrow() {

CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
Iterator<Integer> integerIterator = numbers.iterator();
while (integerIterator.hasNext()) {
    integerIterator.remove();
}

}
结论
CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。发生修改时候做copy,新老版本分离,保证读的高性能,适用于以读为主的情况。

点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
AQS实现原理分析——ReentrantLock
在Java并发包java.util.concurrent中可以看到,不少源码是基于AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是Java并发包的基础工具类,是实现ReentrantLock、CountDownLatch、Semaphore、FutureTask等类的基础。 AQS的主要使用方式是继承,子类通
Wesley13 Wesley13
3年前
java并发之CopyOnWriteArraySet
java并发之CopyOnWriteArraySetCopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,持有CopyOnWriteArrayList的内部对象,它的迭代器也是CopyOnWriteArrayList的迭代器,add操作通过addAllAbsent
九路 九路
3年前
一行一行源码分析清楚AbstractQueuedSynchronizer
在分析Java并发包java.util.concurrent源码的时候,少不了需要了解AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是Java并发包的基础工具类,是实现ReentrantLock、CountDownLatch、Semaphore、FutureTask等类的基础。Google一下A
Wesley13 Wesley13
3年前
Java CopyOnWrite容器
   CopyOnWrite简称COW(写时复制),是一种程序设计中的优化策略,读取时,直接读取,写入时,copy一个副本,在这个副本上进行写入,写入完成,用副本替换原数据,这是一种延时懒惰策略。   从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,CopyOnWriteArrayList和CopyOnW
Wesley13 Wesley13
3年前
Java 并发数据结构
\TOCM\因为Java提供了一些非线程安全的数据结构如HashMap,ArrayList,HashSet等。所有在多线程环境中需要使用支持并发访问操作的数据结构。并发ListVector,CopyOnWriteArrayList是线程安全的List。ArrayList是线程不安全的。如果一定要使用,需要:Collection
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
ReentrantReadWriteLock实现原理
  在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用。在实际开发场景中,在使用共享资源时,可能读操作远远多于写操作。这种情况下,如果对这部分共享资源能够让多个线程读的时候不受阻塞,仅仅在写的时候保证安全性,这样效率会得到显著提升。读写锁Reentra
Wesley13 Wesley13
3年前
J.U.C并发包诞生的那些事儿
前言J.U.C是java包java.util.concurrent的简写,中文简称并发包,是jdk1.5新增用来编写并发相关的基础api。java从事者一定不陌生,同时,流量时代的今天,并发包也成为了高级开发面试时必问的一块内容,本篇内容主要聊聊J.U.C背后的哪些事儿,然后结合LockSupport和Unsafe探秘下并发包更底层的哪些代码,有可
Stella981 Stella981
3年前
CopyOnWriteArrayList 介绍
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(