欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CopyOnWriteArrayList源码分析

程序员文章站 2022-07-14 16:09:59
...

前言:

当我们想要用ArrayList,又想要保证线程安全的时候,可以考虑使用CopyOnWriteArrayList这个类。因为如果使用Vector的话,虽然可以保证线程安全,但是因为在Vector里面是用synchronized修饰的,所以开销会比较大。因此考虑使用CopyOnWriteArrayList。

一.概述

CopyOnWriteArrayList是concurrent 并发包下的一个类,由名称可以看出他是基于数据的一个链表,这个链表在进行写操作的时候回进行copy,接下来就通过源码来看看这个类。

二.源码分析

1.属性:

public class CopyOnWriteArrayList<E>                                     
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {  
    private static final long serialVersionUID = 8673264195747942595L;   
                                                                         
    /** The lock protecting all mutators */                              
    final transient ReentrantLock lock = new ReentrantLock();            
                                                                         
    /** The array, accessed only via getArray/setArray. */               
    private transient volatile Object[] array;                           

    final Object[] getArray() {      
        return array;                
    }                                
                                 
    /**                              
     * Sets the array.               
     */                              
    final void setArray(Object[] a) {
        array = a;                   
    }                                
    public CopyOnWriteArrayList() { 
        setArray(new Object[0]);    
    }                               

首先先看CopyOnWriteArrayList这个类的相关属性,可以看到他实现了RandomAccess接口,所以支持随机访问的。然后定义了一个不被序列化的ReentranLock实例对象,通过这个对象实现了加锁同步的操作,关于这个ReentranLock这个类可以参照这个:https://blog.csdn.net/striveb/article/details/83421107,此外,还有一个不被序列化并且具有可见性的数组,在构造方法里面生成了这样的一个空数组。

2.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();                                                    
    }                                                                     
}                                                                         

由上面源码可以看出,添加元素,即写操作是通过ReentranLock加锁实现的。由上面属性可知,写操作是先复制一个数组,然后在这个数组上进行新增元素,写操作结束之后将原始数组指向新的复制数组。

3.get方法

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

由上面源码可以看出,读操作就是直接返回数组对应的值,并没有加锁。

4.set方法

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 semantic
            setArray(elements);                                  
        }                                                        
        return oldValue;                                         
    } finally {                                                  
        lock.unlock();                                           
    }                                                            
}                                                                

由上面源码可知,set方法也是通过加锁实现修改元素的,其中也是先复制了数组,跟add方法基本相似。

三、使用场景

由上面的分析可知,CopyOnWriteArrayList在添加元素和修改元素的操作上都进行了加锁,所以在两个操作上是会消耗一定的性能的,因此这个类主要使用在读多写少的场景上。