Java中的拷贝数组CopyOnWriteArrayList详解

 更新时间:2023年12月20日 09:11:18   作者:Brain_L  
这篇文章主要介绍了Java中的拷贝数组CopyOnWriteArrayList详解,ArrayList和LinkedList都不是线程安全的,如果需要线程安全的List,可以使用synchronizedList来生成一个同步list,但是这个同步list的方法都是通过synchronized修饰来保证同步的,需要的朋友可以参考下

CopyOnWriteArrayList详解

ArrayList和LinkedList都不是线程安全的,如果需要线程安全的List,可以使用Collections.synchronizedList来生成一个同步list,但是这个同步list的方法都是通过synchronized修饰来保证同步的,并发性能不高。那么如何提高并发性能呢?比如某些场景下,对List的读操作远多于写操作,那么CopyOnWriteArrayList就派上用场了。

1、属性

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

lock是用来在写操作时加锁使用的,具体使用下面方法部分再看。

数组array是CopyOnWriteArrayList的核心部分了,所有的元素都存放在这个数组中。为了保证可见性,所以被volatile修饰着。

2、方法

final Object[] getArray() {
    return array;
}
final void setArray(Object[] a) {
    array = a;
}
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
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);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

三个构造函数都是为了给array数组赋值,生成初始数组。

下面看下它的几个关键方法:get、add、remove

private E get(Object[] a, int index) {
    return (E) a[index];
}
/**
 * {@inheritDoc}
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    return get(getArray(), index);
}

get比较简单,就是直接取数组索引处的元素。注意get方法没有加锁。

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    //1、加锁
    lock.lock();
    try {
        //2、取原数组
        Object[] elements = getArray();
        int len = elements.length;
        //3、拷贝生成新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //4、新元素加到数组最后一位
        newElements[len] = e;
        //5、数组替换
        setArray(newElements);
        return true;
    } finally {
        //6、释放锁
        lock.unlock();
    }
}

add方法,上来就先加锁,然后取出原数组后拷贝生成了一个新的数组,注意,此时原有的array数组没有变,get访问时还是跟之前一样。当把新的数组替换掉array后,由于是volatile修饰的,get访问时就会访问添加过元素的新数组。这样就保证了读写同时进行时,读不需要加锁依然不会有并发问题。最后释放锁后,别的写操作获得锁,再次进行替换操作,这样保证写操作与写操作之间不会有并发问题。

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    //1、获取锁
    lock.lock();
    try {
        //2、取原数组
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        //3、如果被删除元素是数组最后一位,直接截取len-1的新数组
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        //4、否则,分段拷贝生成新数组
        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 {
        //5、释放锁
        lock.unlock();
    }
}

remove与add同理,也是先获取锁,同时生成新的数组之后再把原有的数组进行替换。

3、总结

  • 所有的元素均存储到数组中
  • 写操作(add/set/remove等)会改变数组结构,采用新生成一个数组然后进行替换的做法,保证了在此期间原数组可以正常访问。同时操作之前需要先获取锁,避免写操作之间产生并发问题。
  • 读操作由于不需要改变数组结构,且写操作时,对原有的数组不进行修改,此时仍可正常读取。写操作将新数组进行替换后,由于数组被volatile修饰,保证了可见性,此时也可正正常读取。所以读操作不需要加锁。
  • 因为每次写操作都会带来数组拷贝,所以当读操作远大于写操作时,才可考虑使用此容器。

到此这篇关于Java中的拷贝数组CopyOnWriteArrayList详解的文章就介绍到这了,更多相关CopyOnWriteArrayList详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java经典用法总结(二)

    Java经典用法总结(二)

    这篇文章主要介绍了Java经典用法总结,在本文中,尽量收集一些java最常用的习惯用法,特别是很难猜到的用法,本文重点讲解了java应用和输入输出常用方法,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • JAXB命名空间及前缀_动力节点Java学院整理

    JAXB命名空间及前缀_动力节点Java学院整理

    这篇文章主要给大家介绍了关于JAXB命名空间及前缀的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-08-08
  • idea2019.2安裝MybatisCodeHelper插件的超详细教程

    idea2019.2安裝MybatisCodeHelper插件的超详细教程

    这篇文章主要介绍了idea2019.2安裝MybatisCodeHelper插件的教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • java微信支付接入流程详解

    java微信支付接入流程详解

    这篇文章主要为大家详细介绍了java微信支付接入流程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • mac系统刚安装的idea打不开的问题及解决

    mac系统刚安装的idea打不开的问题及解决

    这篇文章主要介绍了mac系统刚安装的idea打不开的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • SpringBoot创建自定义starter详解

    SpringBoot创建自定义starter详解

    这篇文章主要介绍了SpringBoot创建自定义starter详解,Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的Bean根据环境(条件)进行自动配置,需要的朋友可以参考下
    2024-01-01
  • java单例模式学习示例

    java单例模式学习示例

    java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种,下面提供了单例模式的示例
    2014-01-01
  • MyBatis存储过程、MyBatis分页、MyBatis一对多增删改查操作

    MyBatis存储过程、MyBatis分页、MyBatis一对多增删改查操作

    本文通过一段代码给大家介绍了MyBatis存储过程、MyBatis分页、MyBatis一对多增删改查操作,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
    2016-11-11
  • Java Synchronize底层原理总结

    Java Synchronize底层原理总结

    这篇文章主要给大家总结了Java Synchronize底层原理,文中的图文讲解介绍的非常详细,对我们学习Java Synchronize有一定的帮助,需要的朋友可以参考下
    2023-06-06
  • SpringSecurity自定义Form表单使用方法讲解

    SpringSecurity自定义Form表单使用方法讲解

    这篇文章主要介绍了Spring Security自定义Form表单使用方法,虽然 Spring Security 提供了默认的登录表单,实际项目里肯定是不可以直接使用的,当然 Spring Security 也提供了自定义登录表单的功能
    2023-01-01

最新评论