前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >juc系列-CopyOnWriteArrayList

juc系列-CopyOnWriteArrayList

作者头像
topgunviper
发布2022-05-12 14:29:54
3070
发布2022-05-12 14:29:54
举报
文章被收录于专栏:啥都有的专栏啥都有的专栏

CopyOnWriteArrayList是一个ArrayList线程安全的变体。当数组内容有所变化时,拷贝一份新的出来,在新对象上进行修改操作,完成后把新对象引用赋值给array属性。每发生一次改变,就需要复制一份数据,这样复制是需要一定开销的,所以CopyOnWriteArrayList适合读操作远大于修改操作的情况中。

CopyOnWriteArrayList构造函数:

代码语言:javascript
复制
public CopyOnWriteArrayList()

//Collection做初始化参数
public CopyOnWriteArrayList(Collection<? extends E> c)

//Array做初始化参数
public CopyOnWriteArrayList(E[] toCopyIn)

1.读操作

代码语言:javascript
复制
privateE get(Object[] a, intindex) {
    return(E) a[index];
}
 
publicE get(intindex) {
    returnget(getArray(), index);
}

直接读取,不需要加锁,因为即使读取过程中有其他线程改动List,也是开辟新的数组并在新数组上改动,旧数组对象始终是可用的。

2.写操作 在CopyOnWriteArrayList中写操作过程大致是这样的。在原有数组的基础上拷贝一份新的数组(容器副本),将改动操作在新数组上完成(即把新增元素加入新数组中),然后再把新数组对象的引用赋给CopyOnWriteArrayList的array。显然,在多线程环境中,为了保证线程安全,整个过程需要加锁。所以CopyOnWriteArrayList的写操作性能损耗是很大的,一方面需要竞争获取锁,另一方面需要进行复制操作。

下面以add(int index, E element)方法为例说明CopyOnWriteArrayList的修改操作:

代码语言:javascript
复制
//指定位置增加元素
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        //修改array前获取锁
        lock.lock();
        try {
        //获取原有array
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
            //index=length,即数组尾部新增一个元素,同add(E element)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
            //两次复制
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //新增元素
            newElements[index] = element;
            //将新数组引用赋值给array
            setArray(newElements);
        } finally {
        //释放锁
            lock.unlock();
        }
    }

除了add方法,还有remove、removeRange、addIfAbsent等其他修改操作原理都是一样的,都是新new一个数组对象,在新array上进行修改操作,完事后再将新数组引用赋值给实例变量array,当然修改操作都是需要加锁的。

通过Iterator迭代器遍历CopyOnWriteArrayList

通过Iterator遍历CopyOnWriteArrayList的时候,不允许对array进行修改。remove、add、set方法直接抛出UnsupportedOperationException异常,这点是和普通list不同的地方。

线程安全性

我们可以看到,CopyOnWriteArrayList内部的array数组对象从被创建,到这个对象生命结束,是不可变的。变的是array变量的引用值,每做一次修改操作,array变量就指向新生成的数组对象,原对象被gc,如此反复。这种方式核心思想是减少锁竞争,从而提高高并发时的读取性能,但一定程度上牺牲了写的性能。 由此可得:“写入时复制(Copy-On-Write)”容器的线程安全性在于:只有正确地发布一个事实不可变的对象,那么在访问该对象时就不需要做同步操作。这也就解释了为什么通过迭代器Iterator是不允许进行修改操作的了。

timestamp_1470147855946_test.png

优点

读操作无需加锁,并发环境性能不错,但只适用于读操作远大于写操作的场景。

缺陷
  1. 缺少同步控制,数据的一致性没法保证。在并发环境中,一个线程在修改array的时候,其他线程是可以进行读操作的,只是读取的array任然是旧数据。所以对数据实时一致性要求高的场景,只能另寻它法。
  2. 每次修改都需要进行复制操作,如果list中存放的大对象,则复制时会产生两个对象,会对内存产生一定压力。所以大对象谨慎使用CopyOnWriteArrayList。
CopyOnWriteArraySet

CopyOnWriteArraySet完全基于CopyOnWriteArrayList实现。部分源码如下:

代码语言:javascript
复制
public class CopyOnWriteArraySet<E> extends AbstractSet<E>{
//内部维护了一个CopyOnWriteArrayList对象
    private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

    public CopyOnWriteArraySet(Collection<? extends E> c) {
        al = new CopyOnWriteArrayList<E>();
        al.addAllAbsent(c);
    }
    //其所有操作都是代理给内部的CopyOnWriteArrayList对象执行。
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
    public boolean remove(Object o) {
        return al.remove(o);
    }
    public Iterator<E> iterator() {
        return al.iterator();
    }    
    public Object[] toArray() {
        return al.toArray();
    }
    。。。
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通过Iterator迭代器遍历CopyOnWriteArrayList
  • 线程安全性
  • 优点
  • 缺陷
  • CopyOnWriteArraySet
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档