Java并发编程六之CopyOnWriteArrayList

本文详细介绍了CopyOnWriteArrayList,一种Java并发集合,用于读写分离的场景。它如何工作、如何在实际应用中使用,以及其优缺点。重点讲解了其源码中的add、remove和get方法,适合读多写少的并发场景。
摘要由CSDN通过智能技术生成

CopyOnWriteArrayList是什么

CopyOnWriteArrayList 是jdk1.5以后并发包中提供的一种并发容器,写操作通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也称为“写时复制容器”,类似的容器还有 CopyOnWriteArraySet。

为什么使用CopyOnWriteArrayList

众所周知,集合框架中的ArrayList 是非线程安全的,Vector虽然是线程安全的,但是处理方式简单粗暴(synchronized),性能较差。而CopyOnWriteArrayList提供了不同的处理并发的思路。

CopyOnWriteArrayList 简单使用

public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        final CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList();

        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    list.add(i);
                    System.out.println("t1 write " + i);
                    Thread.sleep(10);
                }
            } catch (Exception e) {
            }
        });

        Thread t2 = new Thread(() ->{
            try {
                for (Integer item : list) {
                    System.out.println("t2 read " + item);
                    Thread.sleep(5);
                }
            }catch (Exception ex){
            }
        });

        Thread t3 = new Thread(() ->{
            try {
                for (Integer item : list) {
                    System.out.println("t3 read " + item);
                    Thread.sleep(3);
                }
            }catch (Exception ex){
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

运行程序,结果输出如下:

t1 write 0
t2 read 0
t3 read 0
t1 write 1
t1 write 2
t1 write 3
t1 write 4

线程t1 是负责 往CopyOnWriteArrayList 写入数据,线程T2 和 T3 负责读取数据。从输出结果可以看出,线程t1 写了5个值,但线程t2和t3只读取到了一个值。数据无法保证实时性,只能保证最终一致性。

CopyOnWriteArrayList实现

很多时候,我们系统中处理的都是读多写少的并发场景。CopyOnWriterArrayList 允许并发的读,读操作是无锁的,性能较高。写操作的话,比如向容器增加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

在这里插入图片描述

CopyOnWriteArrayList优缺点

了解了CopyOnWriteArrayList原理,接下来分析优缺点及使用场景

优点:

读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java 的 list 在遍历时,若中途有其他线程对容器进行修改,则会抛出ConcurrentModificationException 异常。而CopyOnWriteArrayList由于其“读写分离”的思想,遍历和修改操作分别作用在不同的 list容器,所以迭代的时候不会抛出 ConcurrentModificationExecption 异常了。

缺点:

缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,甚至可能引起频繁GC,二是无法保证实时性,Vector 对读写操作均加锁同步,可以保证容器的读写强一致性,CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用于不容容器上,在写的过程中,读是不会发生阻塞的,未切换索引置新容器时,是读不到刚写入的数据的。

CopyOnWriteArrayList源码分析

在这里插入图片描述

  • add(E e) 方法
 /** The array, accessed only via getArray/setArray. */
    // 使用volatile修饰,增加可见性
    private transient volatile Object[] array;
    
 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 加锁,保证线程安全
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //得到数组集合
            Object[] elements = getArray();
            //数组集合的长度
            int len = elements.length;
            //拷贝一个新的数组集合,新集合长度=旧集合长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //设置新集合的最后一个元素等于e
            newElements[len] = e;
            //底层的数组指向新集合
            setArray(newElements);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
  • remove(int index) 方法
/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 原容器
            Object[] elements = getArray();
            // 原容器长度
            int len = elements.length;
            // 旧值
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            // 删除最后一位元素
            if (numMoved == 0)
                // 拷贝新容器并设置array指向新容器
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                // 新容器
                Object[] newElements = new Object[len - 1];
                // 拷贝删除索引前部分的元素
                System.arraycopy(elements, 0, newElements, 0, index);
                // 拷贝删除索引后部分的元素
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // array指向新容器 
                setArray(newElements);
            }
            return oldValue;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
  • get(int index) 方法
 /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

  @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值