并发容器CopyOnWriteArrayList解析

ArrayList是线程不安全的,在早期可以用Vector代替,或者利用Collections工具类,通过包装实现线程安全,但是这两种方方式效率低下。来看一下Vector源码
在这里插入图片描述
可以看出它实现线程安全的方式就是在方法上加锁,我们知道synchronized关键字是独占的效率低下,再看下利用Collections工具类,通过包装:

Collections.synchronizedList(new ArrayList<String>());
  public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

在这里插入图片描述
在底层是通过同步代码块实现的线程安全,且锁对象是一个,所以效率低下,所以JDK1.5之后出现了CopyOnWriteArrayList,既能保证线程安全,效率又没有大大减低。类似的还有CopyOnWriteArraySet。

CopyOnWriteArrayList原理是对其修改在在副本的基础上,所以可以一边迭代一边修改,这和ArrayList明显的区别,特别适合迭代数据比较多的情况,CopyOnWriteArrayList是对读写锁的升级,读操作是不需要加锁的,写操作也不会阻塞读操作,只有写写操作才需要同步,而读写锁除了读读共享其他操作都是互斥的。

CopyOnWriteArrayList适合读多写少的情况,而且读取可以尽量的快,即使写慢一点也可以。

重要方法分析重点是修改操作

//底层使用一个数组表示且被volatile修饰保证了可见性
 /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;
	 /**
     *
     * 从 CopyOnWriteArraySet class.获得底层数组
     */
    final Object[] getArray() {
        return array;
    }
 /**
     * 设置数组
     */
    final void setArray(Object[] a) {
        array = a;
    }

 /**
     * 替换指定位置元素的值
     *
     * @throws越界异常
     */
    public E set(int index, E element) {
    //写写操作是互斥的
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //获取数组
            Object[] elements = getArray();
            //获取但前值
            E oldValue = get(elements, index);

            if (oldValue != element) {
                int len = elements.length;
               //数组复制
                Object[] newElements = Arrays.copyOf(elements, len);
                //值替换
                newElements[index] = element;
                //完事儿副本转正
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            //返回旧的值
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
  /**
     * 最加元素到数组尾巴上.
     *
     * 
     * @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;
            //数组复制
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //转正
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

总之写操作时需要加锁的,所以写写互斥,而且写是在副本的基础上修改,所以根本不需要阻塞读操作,所以可以迭代的同时修改,看一下读取操纵源码:

 /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }


  // Positional Access Operations

    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

我们可以看到读取就是数组的简单读取元素操作,没加任何同步操作。

正因为修改操作是在副本的基础上,所以会浪费内存,而且只能保证最终数据的一致性,不能保证实时一致性,如果要保证实时一致性就别用CopyOnWrite

/**
 * 描述:     对比两个迭代器
 */
public class CopyOnWriteArrayListDemo2 {

    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1, 2, 3});

        System.out.println(list);

        Iterator<Integer> itr1 = list.iterator();

        list.remove(2);
        Thread.sleep(1000);
        System.out.println(list);

        Iterator<Integer> itr2 = list.iterator();

        itr1.forEachRemaining(System.out::println);
        itr2.forEachRemaining(System.out::println);

    }
}

在这里插入图片描述
数据确定是迭代器产生时,而不是迭代器产生之后。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值