【从入门到放弃-Java】并发编程-JUC-CopyOnWriteArrayList

简介: 前言上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList直接来看源码。

前言

上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList
直接来看源码。

CopyOnWriteArrayList

add

/**
 * 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) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        //拷贝一份array,数组大小加一
        es = Arrays.copyOf(es, len + 1);
        //设置最后一位为需要添加的值e
        es[len] = e;
        //将新的array设置为当前List的中的值,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(es);
        return true;
    }
}

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        Object[] newElements;
        int numMoved = len - index;
        
        if (numMoved == 0)
            //如果是在尾部插入,则将List中的数组直接copy并将长度加一
            newElements = Arrays.copyOf(es, len + 1);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index的位置
            newElements = new Object[len + 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        //在index出设置element的值
        newElements[index] = element;
        setArray(newElements);
    }
}

addAll

/**
 * Appends all of the elements in the specified collection to the end
 * of this list, in the order that they are returned by the specified
 * collection's iterator.
 *
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 * @see #add(Object)
 */
public boolean addAll(Collection<? extends E> c) {
    //获取数组
    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
        ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
    if (cs.length == 0)
        return false;
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        Object[] newElements;
        
        if (len == 0 && cs.getClass() == Object[].class)
            //如果当前List长度为0,则直接设置array为新数组
            newElements = cs;
        else {
            //将原数组copy为新的数组,新的数组长度为len+cs.lenth
            newElements = Arrays.copyOf(es, len + cs.length);
            //从len处开始,设置为需要添加的数组
            System.arraycopy(cs, 0, newElements, len, cs.length);
        }
        //将值写入List的成员变量array,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(newElements);
        return true;
    }
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in this list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element
 *        from the specified collection
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 * @see #add(int,Object)
 */
public boolean addAll(int index, Collection<? extends E> c) {
    //获取数组
    Object[] cs = c.toArray();
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        if (cs.length == 0)
            //如果当前List长度为0,直接返回false插入失败
            return false;
        int numMoved = len - index;
        Object[] newElements;
        if (numMoved == 0)
            //如果List尾部插入,则直接在尾部copy要插入的数组
            newElements = Arrays.copyOf(es, len + cs.length);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index到index+cs.length长度的位置
            newElements = new Object[len + cs.length];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index,
                             newElements, index + cs.length,
                             numMoved);
        }
        //将index到index+cs.length填充需要插入的数组
        System.arraycopy(cs, 0, newElements, index, cs.length);
        setArray(newElements);
        return true;
    }
}

get

//get方式就比较简单了 因为array是volatile类型的,不需要任何同步操作就可取到值,保证了并发
public E get(int index) {
    return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

remove

/**
 * 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) {
    //同步锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            //如果是移除最后一个元素,则直接copy 0到len-2位置的元素即可
            newElements = Arrays.copyOf(es, len - 1);
        else {
            //如果不是最后一个元素,则copy需要删除数组[0,index)和[index,length]的元素到新数组。
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If this list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * {@code i} such that {@code Objects.equals(o, get(i))}
 * (if such an element exists).  Returns {@code true} if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
    Object[] snapshot = getArray();
    //找到需要删除元素的索引
    int index = indexOfRange(o, snapshot, 0, snapshot.length);
    return index >= 0 && remove(o, snapshot, index);
}

/**
 * A version of remove(Object) using the strong hint that given
 * recent snapshot contains o at the given index.
 */
private boolean remove(Object o, Object[] snapshot, int index) {
    synchronized (lock) {
        Object[] current = getArray();
        int len = current.length;
        //加锁后验证数组是否在找到要删除的索引后改动过,如果改动的话,删除改动后的第一个o元素
        if (snapshot != current) findIndex: {
            int prefix = Math.min(index, len);
            for (int i = 0; i < prefix; i++) {
                if (current[i] != snapshot[i]
                    && Objects.equals(o, current[i])) {
                    index = i;
                    break findIndex;
                }
            }
            if (index >= len)
                return false;
            if (current[index] == o)
                break findIndex;
            index = indexOfRange(o, current, index, len);
            if (index < 0)
                return false;
        }
        //删除index位置的元素
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1,
                         newElements, index,
                         len - index - 1);
        setArray(newElements);
        return true;
    }
}

iterator

/**
 * Returns an iterator over the elements in this list in proper sequence.
 *
 * <p>The returned iterator provides a snapshot of the state of the list
 * when the iterator was constructed. No synchronization is needed while
 * traversing the iterator. The iterator does <em>NOT</em> support the
 * {@code remove} method.
 *
 * @return an iterator over the elements in this list in proper sequence
 */
public Iterator<E> iterator() {
    //和ArrayList不同,CopyOnWriteArrayList在iterator遍历时,是对当前的array做了一个快照,在遍历期间,array可能会被别的线程更改,但快照不会改变,不受影响,因此在迭代中,数组元素不能改。
    return new COWIterator<E>(getArray(), 0);
}

总结

通过源码分析我们了解到,CopyOnWriteArrayList写操作时,都是以加synchronized锁并copy一份数组进行修改的方式进行的,如果List比较大时,会非常占用资源。

读操作时不用加锁,因为array是volatile的,不需要额外同步,因此读性能非常高。

因此CopyOnWriteArrayList更适用于读多写少的并发操作中。

在遍历时,将当前的array做一个快照,不受其他线程更改的影响。因此,在iterator中,list中的元素是不能更改的(因为更改的是快照,不会写到原数组中,更改一定是无效的)。

但在for循环时,CopyOnWriteArrayList中的元素可以被更改,而ArrayList不行,因为ArrayList的元素被遍历到时总会先检查是否被更改,更改会抛出ConcurrentModificationException

更多文章

见我的博客:https://nc2era.com

written by AloofJr,转载请注明出处

目录
相关文章
|
5天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
5天前
|
数据采集 安全 Java
Java并发编程学习12-任务取消(上)
【5月更文挑战第6天】本篇介绍了取消策略、线程中断、中断策略 和 响应中断的内容
30 4
Java并发编程学习12-任务取消(上)
|
1天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
21 2
|
1天前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
15 2
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
12 5
|
2天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
2天前
|
安全 Java
Java一分钟之-并发编程:原子类(AtomicInteger, AtomicReference)
【5月更文挑战第18天】Java并发编程中的原子类如`AtomicInteger`和`AtomicReference`提供无锁原子操作,适用于高性能并发场景。`AtomicInteger`支持原子整数操作,而`AtomicReference`允许原子更新对象引用。常见问题包括误解原子性、过度依赖原子类以及忽略对象内部状态的并发控制。要避免这些问题,需明确原子操作边界,合理选择同步策略,并精确控制原子更新。示例代码展示了如何使用这两个类。正确理解和使用原子类是构建高效并发程序的关键。
12 1
|
2天前
|
安全 Java 容器
Java一分钟之-并发编程:并发容器(ConcurrentHashMap, CopyOnWriteArrayList)
【5月更文挑战第18天】本文探讨了Java并发编程中的`ConcurrentHashMap`和`CopyOnWriteArrayList`,两者为多线程数据共享提供高效、线程安全的解决方案。`ConcurrentHashMap`采用分段锁策略,而`CopyOnWriteArrayList`适合读多写少的场景。注意,`ConcurrentHashMap`的`forEach`需避免手动同步,且并发修改时可能导致`ConcurrentModificationException`。`CopyOnWriteArrayList`在写操作时会复制数组。理解和正确使用这些特性是优化并发性能的关键。
9 1
|
2天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第18天】在Java并发编程中,锁是一种常用的同步机制,用于保护共享资源的访问。然而,不当的锁使用可能导致性能问题和死锁风险。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁分离和读写锁等技术,以提高并发程序的性能和可靠性。
|
3天前
|
Java 编译器
Java 并发编程中的锁优化策略
【5月更文挑战第17天】在 Java 并发编程中,锁是一种常见的同步机制,用于保护共享资源的访问。然而,不当使用锁可能导致性能问题和死锁风险。本文将探讨 Java 中的锁优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。