Java 并发编程之 CopyOnWriteArrayList

Java juc 大约 5127 字

介绍

线程安全的,适合并发的集合类,对比ArrayList

读写分离

写操作是复制一份新的数组,读操作在原数组的快照中进行。

数据一致性

遍历时获取的是原数组的快照,对集合的addremove不会引起并发修改异常,但遍历时无法保证数据是最新数据,只能保证数据的最终一致性。

优点

写操作的同时,允许读操作。

缺点

内存占用:每次写操作都会复制一份数组出来。

数据一致性:只能保存数据最终一致性。

代码示例

public class CopyOnWriteArrayListDemo {

    private static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(ThreadLocalRandom.current().nextInt(10000));
            }).start();
        }
        new Thread(() -> {
            Iterator<String> iterator = set.iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                System.out.println(next);
                if (Integer.parseInt(next) % 2 == 0) {
                    set.remove(next);
                }
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);

        System.out.println(list.toString());

    }

}

输出:

3527
7148
1178
7453
4962
9133
9904
5979
4204
9213
[3527, 7453, 9133, 5979, 9213]

源码解析

基于Java8

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    // 悲观锁
    final transient ReentrantLock lock = new ReentrantLock();

    // 底层维护的数组
    private transient volatile Object[] array;

    // 无参构造
    public CopyOnWriteArrayList() {
        // 设置一个长度为 0 的 Object 数组
        setArray(new Object[0]);
    }

    // 设置数组
    final void setArray(Object[] a) {
        array = a;
    }

    // 添加元素,上悲观锁
    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();
        }
    }

    // 获取元素
    public E get(int index) {
        return get(getArray(), index);
    }

    // 迭代器
    public Iterator<E> iterator() {
        // 构造一个迭代器,传入当前底层数组和游标第 0 位索引
        return new COWIterator<E>(getArray(), 0);
    }

    static final class COWIterator<E> implements ListIterator<E> {

        // CopyOnWriteArrayList 底层数组的快照
        private final Object[] snapshot;
        // 游标
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        // 获取的是快照数组的元素
        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }
    }

    // CopyOnWriteArrayList toString() 方法也是返回当前底层数组,只能保持最终一致性
    public String toString() {
        return Arrays.toString(getArray());
    }

}    

与 Vector 对比

CopyOnWriteArrayList效率更高,Vector只能同时处理一种操作(先进入add()方法,则必须等add()方法执行完毕后才能执行其他方法,如:get())。

遍历时删除Vector会抛出并发修改异常,CopyOnWriteArrayList不会抛出异常,但是只能保证数据最终一致性,因为遍历的是原数组的快照。

Vector 代码示例

public class VectorDemo {

    public static void main(String[] args) {
        Vector<String> vector = new Vector<String>(){
            @Override
            public synchronized boolean add(String o) {
                System.out.println(LocalDateTime.now() + " 添加数据开始");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(LocalDateTime.now() + " 添加数据完成");
                return super.add(o);
            }

            @Override
            public synchronized String get(int index) {
                System.out.println(LocalDateTime.now() + " 获取数据完成");
                return super.get(index);
            }
        };


        new Thread(() -> {
            vector.add("1234");
        }).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(LocalDateTime.now() + " 获取数据开始");
            System.out.println(vector.get(0));
        }).start();

        TimeUnit.SECONDS.sleep(5);

        for (String s : vector) {
            vector.remove("1234");
        }

    }

}

输出:

2021-04-02T20:46:20.856 添加数据开始
2021-04-02T20:46:21.805 获取数据开始
2021-04-02T20:46:23.857 添加数据完成
2021-04-02T20:46:23.857 获取数据完成
1234

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.Vector$Itr.checkForComodification(Vector.java:1184)
    at java.util.Vector$Itr.next(Vector.java:1137)
    at container.VectorDemo.main(VectorDemo.java:50)
阅读 85 · 发布于 2021-10-31

————        END        ————

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批