前言
今天我们一起学习下 java.util.concurrent 并发包里的工具类. 当有多个线程可能同时遍历, 修改某个公共数组时候, 如果不希望因使用 synchronize 关键字锁住整个数组而影响性能, 可以考虑使用 CopyOnWriteArrayList.
CopyOnWriteArrayList API
CopyOnWriteArrayList 的定义如下:
- public class CopyOnWriteArrayList<E>
- extends Object
- implements List<E>, RandomAccess, Cloneable, Serializable
它也属于 Java 集合框架的一部分, 是 ArrayList 的线程安全的变体, 跟 ArrayList 的不同在于: CopyOnWriteArrayList 针对数组的修改操作 (add,set 等) 是基于内部拷贝的一份数据而进行的. 换句话说, 即使在一个线程进行遍历操作时有其他线程可能进行插入或删除操作, 我们也可以 "线程安全" 得遍历 CopyOnWriteArrayList.
例子 1: 插入 (删除) 数据的同时进行遍历
CopyOnWriteArrayList 的实现原理是, 在一个线程开始遍历 (创建 Iterator 对象) 时, 内部会创建一个 "快照" 数组, 遍历基于这个快照 Iterator 进行, 在遍历过程中这个快照数组不会改变, 也就不会抛出 ConcurrentModificationException. 如果在遍历的过程中有其他线程尝试改变数组的内容, 就会拷贝一份新的数据进行变更, 而后面再来访问这个数组的线程, 看到的就是变更过的数组.
创建一个 CopyOnWriteArrayList 数组 numbers;
CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
创建一个遍历器 iterator;
Iterator<Integer> iterator = numbers.iterator();
给 numbers 中增加 (或删除, 修改) 一个元素;
numbers.add(100);
利用 iterator 遍历数组的元素, 发现遍历的结果是 Iterator 对象创建之前的;
- List<Integer> result = new LinkedList<>();
- iterator.forEachRemaining(result::add);
- assertThat(result).containsOnly(1, 3, 5, 78);
完整的例子如下:
- package org.java.learn.concurrent.copyonwritearraylist;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import static org.assertj.core.API.Assertions.*;
- /**
- * 作用:
- * User: duqi
- * Date: 2017/11/9
- * Time: 11:20
- */
- public class CopyOnWriteArrayListExample {
- public static void main(String[] args) {
- CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
- Iterator<Integer> iterator = numbers.iterator();
- numbers.add(100);
- List<Integer> result = new LinkedList<>();
- iterator.forEachRemaining(result::add);
- assertThat(result).containsOnly(1, 3, 5, 78);
- Iterator<Integer> iterator2 = numbers.iterator();
- numbers.remove(3);
- List<Integer> result2 = new LinkedList<>();
- iterator2.forEachRemaining(result2::add);
- assertThat(result2).containsOnly(1, 3, 5, 78, 100);
- }
- }
例子 2: 不支持一边遍历一边删除
由于 CopyOnWriteArrayList 的实现机制 -->修改操作和读操作拿到的 Iterator 对象指向的不是一个数组, 因此不支持基于 Iterator 对象的方法结果的删除: public void remove();, 例子代码如下:
- package org.java.learn.concurrent.copyonwritearraylist;
- import java.util.Iterator;
- import java.util.concurrent.CopyOnWriteArrayList;
- /**
- * 作用: User: duqi Date: 2017/11/9 Time: 13:40
- */
- public class CopyOnWriteArrayListExample2 {
- public static void main(String[] args) {
- try {
- testExceptionThrow();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static void testExceptionThrow() {
- CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
- Iterator<Integer> integerIterator = numbers.iterator();
- while (integerIterator.hasNext()) {
- integerIterator.remove();
- }
- }
- }
结论
CopyOnWriteArrayList 适合使用在读操作远远大于写操作的场景里, 比如缓存. 发生修改时候做 copy, 新老版本分离, 保证读的高性能, 适用于以读为主的情况.
参考资料
Guide to CopyOnWriteArrayList
CopyOnWriteArrayList 详解 https://my.oschina.net/jielucky/blog/167198
官方文档: CopyOnWriteArrayList
本号专注于后端技术, JVM 问题排查和优化, Java 面试题, 个人成长和自我管理等主题, 为读者提供一线开发者的工作和成长经验, 期待你能在这里有所收获.
来源: https://www.cnblogs.com/javaadu/p/11229161.html