JDK源码解析之CopyOnWriteArrayList

前言:

    之前介绍了关于List的常用两种实现类:ArrayList、LinkedList。也从源码的角度介绍下这两种List的使用场景。

    但是在多线程的场景中,这两种List类型就不适合使用了。我们需要使用多线程安全的List类来完成集合操作。

    JDK也会我们提供了线程安全的List。本篇文章就介绍下Vector和CopyOnWriteArrayList

1.Vector

    先说下这个类,这个类的属性方法同ArrayList一致,所以不再分析其源码。

    那它为什么能够实现线程安全操作呢?主要就是因为Vector在其主要操作方法上都加上了synchronized关键字,这样就完成了线程安全操作

    既然已经有了这个线程安全的List类,为啥还要CopyOnWriteArrayList?读者可以先自行思考下,最后会来解答

2.CopyOnWriteArrayList结构分析

/** A thread-safe variant of {@link java.util.ArrayList}  */
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    // 实现线程安全的大杀器
    final transient ReentrantLock lock = new ReentrantLock();
    
    // 使用数组来存储数据
    private transient volatile Object[] array;

    相对ArrayList而言,多了一个用于线程安全的lock,少了计数的size

3.CopyOnWriteArrayList构造方法

// 1.空参构造
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

// 2.集合参数构造
public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    // 解析对应的数组项
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    
    // 赋值给当前array
    setArray(elements);
}

// 3.数组参数构造
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

// 来看一下setArray()方法
final void setArray(Object[] a) {
    array = a;
}

    可以看到,主要就是将传入的参数解析为一个数组对象,然后赋值给当前array

4.添加操作

    同ArrayList的添加操作

// 1.添加单个元素
public boolean add(E e) {
    ...
}

// 2.添加单个元素到某个位置
public void add(int index, E element) {
    ...
}

// 3.添加集合元素
public boolean addAll(Collection<? extends E> c) {
    ...
}

// 4.添加集合从某个位置开始
public boolean addAll(int index, Collection<? extends E> c) {
    ...
}

    主要分析一下add(E e)方法

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 1.获取lock对象锁
    lock.lock();
    try {
        // 2.同ArrayList类似的操作
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        // 3.确保释放锁
        lock.unlock();
    }
}

    总结:可以看到,有关于线程安全操作主要就是通过ReentrantLock来实现的。    

    需要注意的是:CopyOnWriteArrayList并没有类似于ArrayList的自动扩容(1.5倍扩容)操作,添加一次执行Arrays.copyOf操作,这个会成为一个性能瓶颈

    对ReentrantLock不太熟悉的同学可以专门找些文章来看下,本篇不再详述

5.删除操作

E	remove(int index)
    Removes the element at the specified position in this list.
    
boolean	remove(Object o)
    Removes the first occurrence of the specified element from this list, if it is present.
    
boolean	removeAll(Collection<?> c)
    Removes from this list all of its elements that are contained in the specified collection.
    
boolean	removeIf(Predicate<? super E> filter)
    Removes all of the elements of this collection that satisfy the given predicate.

    我们在这里主要看一下remove(int index)操作

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    // 1.获取lock对象锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 2.获取index位置元素
        // 注意,这里没有进行边界检查,直接走的array[index]
        // 所以,如果index超过边界,则直接抛错
        E oldValue = get(elements, index);
        
        // 3.下面就跟ArrayList的操作差不多
        int numMoved = len - index - 1;
        if (numMoved == 0)
            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);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        // 4.释放锁
        lock.unlock();
    }
}

    总结:ReentrantLock的lock和unlock方法实现了线程安全操作。

    注意与ArrayList的操作不同点:没有对index进行边界检查。超过边界则直接抛错

6.修改操作

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 1.获取lock对象锁
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        // 2.直接获取元素,并替换
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // 如果新老元素相同,则不置操作
            setArray(elements);
        }
        return oldValue;
    } finally {
        // 4.释放锁
        lock.unlock();
    }
}

7.查询操作

E	get(int index)
    Returns the element at the specified position in this list.
    
int	indexOf(E e, int index)
    Returns the index of the first occurrence of the specified element in this list, searching forwards from index, or returns -1 if the element is not found.
    
int	indexOf(Object o)
    Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.

    我们直接来看下get(int index)操作

public E get(int index) {
    return get(getArray(), index);
}

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

    可以看到,跟ArrayList的主要区别就是:没有区别

    这时候可以来回答下我们再讲解Vector时候提出的问题:既然已经有了Vector这个线程安全的List类,为什么还需要CopyOnWriteArrayList?

    来看下Vector的查询操作

// Vector.get()
public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

    在Vector中,无论增删操作,还是查询操作,统统都加上synchronized锁,而在CopyOnWriteArrayList中,只有增删改操作添加了锁,查询则没有。

    实际在一个集合中,有大部分操作都是查询操作,所以在没有添加锁的情况下,CopyOnWriteArrayList的查询是并行的,多线程可以同时来查询,而Vector只能线性查询,同一时刻只能有一个线程在查询。

总结:相比Vector使用synchronized关键字对每一个方法进行加锁操作,CopyOnWriteArrayList使用ReentrantLock灵活的在增删改方法上主动获取和释放锁,而针对于使用率比较高的查询方法则没有加锁操作,使多线程可以并行访问该方法

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/86081573