CopyOnWriteArrayList源码解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:   CopyOnWriteArrayList是java1.5版本提供的一个线程安全的ArrayList变体。 在讲解5.1.1ArrayList的时候,有说明ArrayList的fail-fast特性,它是指在遍历过程中,如果ArrayList内容发生过修改,会抛出ConcurrentModificationException。

 

 

CopyOnWriteArrayListjava1.5版本提供的一个线程安全的ArrayList变体。

在讲解5.1.1ArrayList的时候,有说明ArrayListfail-fast特性,它是指在遍历过程中,如果ArrayList内容发生过修改,会抛出ConcurrentModificationException

在多线程环境下,这种情况变得尤为突出,参见测试代码(代码基于java8):

 

ArrayList<Integer> list = new ArrayList<>();

ExecutorService executorService = Executors.newFixedThreadPool(10);

for (int i = 0; i < 100; i++) {

// 启动一个写ArrayList的线程

executorService.execute(() -> {

list.add(1);

});

// 启动一个读ArrayList的线程

executorService.execute(() -> {

for (Integer v : list) {

System.out.println(v);

}

});

}

 

 

 

设想下面两种处理方式:

1、不使用迭代器形式转而使用下标来遍历,这就带来了一个问题,读写没有分离,写操作会影响到读的准确性,甚至导致IndexOutOfBoundsException,比如下面的例子:

上述例子在多线程执行过程中,list.remove(0)会减少listsize,而读操作使用的是首次遍历的size,必然会出现严重的运行时异常,所以,遍历下标的方法不可取。

 

              executorService.execute(() -> {

          list.remove(0);

        });

        executorService.execute(() -> {

            for (int x = 0, len = list.size(); x < len; x++) {

              System.out.println(list.get(x));

            }

        });

 

2、不直接遍历list,而是把list拷贝一份数组,再行遍历,比如把读过程修改成下面这样:

executorService.execute(() -> {

    for (Integer v : list.toArray(new Integer[0])) {

        System.out.println(v);

    }

});

 

 

此方法在CopyOnWriteArrayList出现之前较为常见,其本质是把list内容拷贝到了一个新的数组中,CopyOnWriteArrayList也是采取的类似的手段,区别在于,这个例子使用的是CopyOnRead方式,也就是读时拷贝

下面来介绍下CopyOnWriteArrayList写时拷贝的实现方式。

1.1.1.1.1 写时拷贝

写时拷贝,自然是在做写操作时,把原始数据拷贝到一个新的数组,涉及到写操作的是三个方法addremoveset,以add方法为例:

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();

        }

    }

 

 

可以注意到,在每一次add操作里,数组都被copy了一份副本,这就是写时拷贝的原理。

那么,写时拷贝和读时拷贝各有什么优势呢?

如果一个list的遍历操作比写入操作频繁,应该使用CopyOnWriteArrayList,反之,则考虑使用读时拷贝的方式

目录
相关文章
|
22天前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
1月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
55 6
|
6天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
10天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
6天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
|
29天前
|
测试技术 Python
python自动化测试中装饰器@ddt与@data源码深入解析
综上所述,使用 `@ddt`和 `@data`可以大大简化写作测试用例的过程,让我们能专注于测试逻辑的本身,而无需编写重复的测试方法。通过讲解了 `@ddt`和 `@data`源码的关键部分,我们可以更深入地理解其背后的工作原理。
24 1
|
1月前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
68 1
|
1月前
|
开发者 Python
深入解析Python `requests`库源码,揭开HTTP请求的神秘面纱!
深入解析Python `requests`库源码,揭开HTTP请求的神秘面纱!
123 1
|
1月前
|
NoSQL Redis
redis 6源码解析之 ziplist
redis 6源码解析之 ziplist
21 5
|
1月前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践

热门文章

最新文章

推荐镜像

更多