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详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea中增强for循环提示unexpected token问题

    idea中增强for循环提示unexpected token问题

    这篇文章主要介绍了idea中增强for循环提示unexpected token问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 使用SpringBoot编写一个优雅的单元测试

    使用SpringBoot编写一个优雅的单元测试

    这篇文章主要为大家详细介绍了如何使用SpringBoot编写一个优雅的单元测试,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-07-07
  • 浅谈java中文本框和文本区

    浅谈java中文本框和文本区

    本文给大家介绍的是java中的文本框和文本区的概念和使用方法,以及简单的示例,十分实用,有需要的小伙伴可以参考下。
    2015-06-06
  • Java之操作Redis案例讲解

    Java之操作Redis案例讲解

    这篇文章主要介绍了Java之操作Redis案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Spring AOT优化转换的使用原理详解

    Spring AOT优化转换的使用原理详解

    这篇文章主要介绍了Spring AOT优化转换的使用原理,Spring AOT是一种在构建时对Spring应用进行优化的技术,主要为GraalVM原生镜像生成配置,同时提升传统JVM的启动性能,需要的朋友可以参考下
    2025-10-10
  • Java JDK 1.8 lambda的用法详解

    Java JDK 1.8 lambda的用法详解

    这篇文章主要介绍了Java JDK 1.8 lambda的用法详解,文中给大家提到了jdk 1.8 Lambda 表达式 遍历数组的方法,需要的朋友可以参考下
    2019-09-09
  • springboot 默认静态路径实例解析

    springboot 默认静态路径实例解析

    这篇文章主要介绍了springboot 默认静态路径实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 轻轻松松吃透Java并发fork/join框架

    轻轻松松吃透Java并发fork/join框架

    fork/join是一个工具框架 ,本文详细的介绍了fork/join框架的具体使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Spring注解秘籍之如何优雅地使用@RequestHeader

    Spring注解秘籍之如何优雅地使用@RequestHeader

    @RequestHeader注解是Spring MVC中一个强大的工具,它为处理HTTP请求头提供了一种简洁而直观的方法,这篇文章主要介绍了Spring注解秘籍之如何优雅地使用@RequestHeader的相关资料,需要的朋友可以参考下
    2026-04-04
  • java使用@Transactional时常犯的N种错误

    java使用@Transactional时常犯的N种错误

    @Transactional是我们在用Spring时候几乎逃不掉的一个注解,本文主要介绍了使用 @Transactional 时常犯的N种错误,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01

最新评论