1. 为什么需要 CopyOnWriteArrayList

ArrayList 的内部实现是一个数组, 并且是动态扩容的, 当插入数据时, 先判断数组是否需要扩容, 如果需要扩容, 则先扩容, 再插入数据, 也就说插入由三步组成

1) 检查是否需要扩容

2) 扩容/不扩容

3) 数据加入到数组

代码如下

    public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

这里如果出现并发操作, 会有两个问题

1) 如果同时进行扩容, 则有可能出现连续进行两次扩容的问题, 而实际只需要一次

2) 如果同时对数组进行赋值, 则有可能第一个赋值元素被覆盖, 因为可能两个线程拿到的 size 是一样的, 他们都填到数组的同一个槽里

再看另一个 add 操作

    public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

这种情况下, add 分为四步

1) 检查是否需要扩容

2) 扩容

3) 移动数据

4) 插入数据

如果此时有并发的读取和插入操作, 则有可能出现读取到的值为 null 的情况, 例如 list.get(3) 跟 list.add(3, "new") 同时发生, 本来 list.get(3) 应该拿到 "old" 或者 "new", 现在却拿到了 null, 这是因为在取值的过程中正好发生了移动数据, 但是数据又还没被插入到移动的空槽里

2. 如何解决这些问题?

一种最简单的方式是对 ArrayList 的所有行为全部加锁, 例如 Collections.synchronizedList(list) 方法, 他会包装 list, 并对所有操作加锁

但是这种方式会 block 所有操作, 读, 写 都是串行的, 会影响效率

3. CopyOnWriteArrayList 如何解决这些问题

cowlist 的写操作全都加锁, 并且在加锁后会将底层数组复制一份再进行写操作, 当写操作完成以后, 整个替换底层数组

1) 使用锁, 即解决了并发写的问题

2) 读操作不加锁, 效率更高, 读写不冲突

3) 写操作使用副本控制, 解决读操作会读到 null 问题, 因为底层数据不会出现有空槽的中间状态

Java CopyOnWriteArrayList的更多相关文章

  1. java CopyOnWriteArrayList的使用

    除了加锁外,其实还有一种方式可以防止并发修改异常,这就是将读写分离技术(不是数据库上的). 先回顾一下一个常识: 1.JAVA中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个 ...

  2. Java CopyOnWriteArrayList分析

    CopyOnWriteArrayList是一种线程安全的ArrayList,顾名思义,它会利用写时拷贝技术,它对共享对象做仅仅读操作的时候,大家都用一个共享对象,假设有可变的操作时,就会复制一份出来, ...

  3. java.util.ConcurrentModificationException 多线程访问ArrayList引起

    http://blog.csdn.net/androiddevelop/article/details/21509345   Java ConcurrentModificationException ...

  4. JAVA Concurrent包 中的并发集合类

    我们平时写程序需要经常用到集合类,比如ArrayList.HashMap等,但是这些集合不能够实现并发运行机制,这样在服务器上运行时就会非常的消耗资源和浪费时间,并且对这些集合进行迭代的过程中不能进行 ...

  5. HowToDoInJava Java 教程·翻译完成

    原文:HowToDoInJava 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. ApacheCN 学习资源 目录 核心 Java 教程 什 ...

  6. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

  7. JAVA 多线程随笔 (三) 多线程用到的并发容器 (ConcurrentHashMap,CopyOnWriteArrayList, CopyOnWriteArraySet)

    1.引言 在多线程的环境中,如果想要使用容器类,就需要注意所使用的容器类是否是线程安全的.在最早开始,人们一般都在使用同步容器(Vector,HashTable),其基本的原理,就是针对容器的每一个操 ...

  8. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList

    概要 本章是"JUC系列"的CopyOnWriteArrayList篇.接下来,会先对CopyOnWriteArrayList进行基本介绍,然后再说明它的原理,接着通过代码去分析, ...

  9. java并发编程:并发容器之CopyOnWriteArrayList(转)

    原文:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开大家都在共享同一个内容,当某个 ...

随机推荐

  1. CSS VS JS动画,哪个更快[译]

    英文原文:https://davidwalsh.name/css-js-animation 原作者Julian Shapiro是Velocity.js的作者,Velocity.js是一个高效易用的js ...

  2. nodeJs 模块cookie-session api文档中文翻译,偶自己翻译的

    原文英文文档链接点击 说明 简单的 基于cookie的 session中间件 安装 它是一个可以用npm注册的node模块,可以通过npm install命令安装 npm install cookie ...

  3. redis集群同步迁移方法(二):通过redis-migrate-tool实现

    前篇介绍的redis replication方法,操作步骤多,而且容易出错.在git上看到一些开源工具也能实现同步迁移功能,而且步骤简单,比如redis-port,redis-migrate-tool ...

  4. Struts2环境搭建

    1,从http://struts.apache.org  官网下载struts2的源码,最新的源码是2.5版本的,但是考虑到网上2.3版本的教程比较多,所以我下载了一个2.3版本的struts. 2, ...

  5. ORACLE常见数据类型详解

    1.字符类型 • CHAR:一个定长字符串,当位数不足自动用空格填充来达到其最大长度.如非NULL的CHAR(12)总是包含12字节信息.CHAR字段最多可以存储2,000字节的 信息. • VARC ...

  6. 使用二分法查找mobile文件中区号归属地

    #!/usr/bin/env python #coding:utf-8 ''' Created on 2015年12月8日 @author: DL @Description: 使用二分法查找mobil ...

  7. Apache ab压力测试时出现大量的错误原因分析

    最近有一个测试任务,是测试nginx的并发请求到底能够达到多少的, 于是就用ab工具对其进行压力测试. 这压力测试一执行,问题就来了:发起10000次请求,并发100,错误的情况能达到30%--50% ...

  8. IOS 使用KBMMW 访问JAVA 服务

    废话少说,如何使用KBMMW 做个过渡,使IOS 可以使用JAVA 的服务? 其实KBMMW 本身就要java service 的服务,但是为了把这个问题说清楚,我手工做一个例子. 首先,要使用JAV ...

  9. ---Shell的数组遍历

    1.  一一读入:  read -a A < <(echo a b c d e f g) 2. 遍历输出

  10. 偶的《javascript框架设计》终于出版

    #cnblogs_post_body p{ text-indent:2em!important; } 历时两年多,我的书终于付梓出版了.应各方面的要求,写软文一篇,隆重介绍一下此书对各位程序员的钱途有 ...