文档章节

并发容器:CopyOnWriteArrayList

似是而非Sage
 似是而非Sage
发布于 2016/08/16 19:46
字数 732
阅读 25
收藏 0

0.ConpyOnWriteArrayList的使用场景

在多线程中,如果A线程在读取一个List时,B线程在向里面add数据,会抛出异常:java.util.ConcurrentModificationException

public class ErrorTest {

    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        final ArrayList<Integer> list_thrd = new ArrayList<>(list);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (true) {
                    list_thrd.add(count++);
                }
            }
        });

        thread.setDaemon(true);
        thread.start();

        Thread.sleep(3);
        for (Integer integer : list_thrd) {
            System.out.println(integer);
        }
    }

}

运行这段代码,会抛出java.util.ConcurrentModificationException,即主线程在遍历list元素的时候,子线程在向里面添加元素。在这种情况下,我们可以使用CopyOnWriteArrayList进行操作。

1.初步了解CopyOnWriteArrayList

CopyOnWrite,字面意思就是写时复制。通俗的说,就是我们向容器中添加数据的时候,并不是向容器本身添加数据。而是,先copy一个副本,向里面添加记录,然后再将copy赋值给原对象。我们来看下面的代码

public class CopyOnWriteListTest {

    public static void main(String[] args) throws InterruptedException {
        List<Integer> temp = Arrays.asList(1, 2, 3, 4, 5);

        final CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(temp);

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (true) {
                    list.add(count++);
                }
            }
        });

        thread.setDaemon(true);
        thread.start();

        Thread.sleep(3);
        for (Integer integer : list) {
            System.out.println(list.hashCode());
            System.out.println(integer);
        }
    }

}

我们在遍历CopyOnWriteArrayList集合的时候,同时打印出hashCode,可以看到输出结果:

-1813306013
1
1381289098
2
1109838168
3
-1094901371
4
-1008565321
5
-1813169375
0
-1540165649
1
-1441641759
2
1807730922
3
-870434537
4
-1185576945
5

这说明CopyOnWriteArrayList的add方法确实是创建了新的list对象,同时也不会抛出异常。

2.CopyOnWriteArrayList的实现原理

2.1 add方法

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

/** The array, accessed only via getArray/setArray. */
private volatile transient Object[] array;
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();
        }
    }
  • 添加方法使用了重入锁来保证多线程添加的时候,不会生成多个list的副本在内存中;
  • array对象使用了volatile修饰,保证了多线程环境下的可见性
  • 最后使用setArray来给array对象赋值

2.2 get方法

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

get方法没有使用锁,所以读取的还是旧的数据,因为写入的时候并没有锁住旧的array对象

3.CopyOnWriteArrayList的使用场景 

从刚刚的例子中我们可以看出,CopyOnWriteArrayList适合读多写少的场景。例如黑白名单等,将数据初始化到List当中,当有较少写操作的时候,可以保证多并发情况下,List的最终一致性。但是,它也是有缺点的:当执行add操作的时候,由于要copy出一个对象,这时会导致内存占用加大。可能会造成频繁的Young GC和Full GC。

© 著作权归作者所有

共有 人打赏支持
似是而非Sage
粉丝 3
博文 11
码字总数 10369
作品 0
嘉定
后端工程师
并发容器之写时拷贝的 List 和 Set

对于一个对象来说,我们为了保证它的并发性,通常会选择使用声明式加锁方式交由我们的 Java 虚拟机来完成自动的加锁和释放锁的操作,例如我们的 synchronized。也会选择使用显式锁机制来主动...

Single_YAM
2017/12/02
0
0
CopyOnWriteArrayList源码剖析

1.1前言 CopyOnWriteArrayList是JAVA中的并发容器类,同时也是符合写时复制思想的CopyOnWrite容器。写时复制思想即是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容...

筱虾米
06/14
0
0
CopyOnWriteArrayList与Collections.synchronizedMap性能

平台: AMD X2 5400+,2G RAM,JDK6,eclipse 3.4 CopyOnWriteArrayList:专为多线程并发设计的容器,“写入时复制”策略。 Collections.synchronizedMap:同步容器,独占策略。 分析: 可以...

MarkCo
2016/04/20
35
0
Java多线程-并发容器

Java多线程-并发容器 在Java1.5之后,通过几个并发容器类来改进同步容器类,同步容器类是通过将容器的状态串行访问,从而实现它们的线程安全的,这样做会消弱了并发性,当多个线程并发的竞争...

tianfuguoss
2015/12/01
185
0
CopyOnWriteArraylist解析

自从JDK1.5引入引入concurrentHashmap,CopyOnWriteArraylist等并发集合后,java集合体系得到了很大的完善,我们有多了不少特殊情境的选择。 我们都知道,ArrayList存数数据的结构为数组,获...

令飞
2015/04/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JS:异步 - 面试惨案

为什么会写这篇文章,很明显不符合我的性格的东西,原因是前段时间参与了一个面试,对于很多程序员来说,面试时候多么的鸦雀无声,事后心里就有多么的千军万马。去掉最开始毕业干了一年的Jav...

xmqywx
今天
0
0
Win10 64位系统,PHP 扩展 curl插件

执行:1. 拷贝php安装目录下,libeay32.dll、ssleay32.dll 、 libssh2.dll 到 C:\windows\system32 目录。2. 拷贝php/ext目录下, php_curl.dll 到 C:\windows\system32 目录; 3. p...

放飞E梦想O
今天
0
0
谈谈神秘的ES6——(五)解构赋值【对象篇】

上一节课我们了解了有关数组的解构赋值相关内容,这节课,我们接着,来讲讲对象的解构赋值。 解构不仅可以用于数组,还可以用于对象。 let { foo, bar } = { foo: "aaa", bar: "bbb" };fo...

JandenMa
今天
1
0
OSChina 周一乱弹 —— 有人要给本汪介绍妹子啦

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享水木年华的单曲《中学时代》@小小编辑 手机党少年们想听歌,请使劲儿戳(这里) @须臾时光:夏天还在做最后的挣扎,但是晚上...

小小编辑
今天
18
4
centos7安装redis及开机启动

配置编译环境: sudo yum install gcc-c++ 下载源码: wget http://download.redis.io/releases/redis-3.2.8.tar.gz 解压源码: tar -zxvf redis-3.2.8.tar.gz 进入到解压目录: cd redis-3......

hotsmile
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部