Java 并发 --- CopyOnWriteArrayList 源码分析

CopyOnWriteArrayList 介绍(jdk 1.8)

CopyOnWriteArrayList,顾名思义,Write的时候总是要Copy,读写分离的思想,通俗地讲,当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器复制出一个新的容器,然后在新的容器里添加元素,添加玩元素之后再讲原来容器的引用指向新的容器。CopyOnWriteArrayList的读是并发的,不需要加锁,juc包提供了两个使用CopyOnWrite思想实现的并发容器,分别是CopyOnWriteArrayList和CopyOnWriteArraySet(CopyOnWriteArraySet内部持有一个CopyOnWriteArrayList,很多方法都是调用CopyOnWriteArrayList来实现的)。

继承体系

这里写图片描述

数据结构

/** The lock protecting all mutators */
//可重入锁,用于数据的调整
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
// 存储数据的数组
private transient volatile Object[] array;

构造方法

1、默认构造

/**
 * Sets the array.
 */
final void setArray(Object[] a) {
    array = a;
}

/**
 * Creates an empty list.
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

2、通过集合初始化

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    //如果c 是CopyOnWriteArrayList 类型,直接调用其getArray 方法
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        //调用c的toArray 方法
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] 
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

get 操作

因为是对数组的访问,因此可以做到随机访问,速度很快,同时读取操作,不需要进行同步操作。

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

添加操作

1、add(E e) 添加元素到数组末尾,允许元素为null

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //copy到新的数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //添加元素
        newElements[len] = e;
        //重新设置引用关系
        setArray(newElements);
        return true;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2、add(int index, E element) 添加元素到指定的位置

public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0) //添加元素到末尾
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            newElements = new Object[len + 1];
            // 空出index 位置
            // 先把原数组index前面的元素复制到新的数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            // 再把原数组index后面的元素复制到新的数组中,这样index的位置就空缺出来了
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        // 添加元素
        newElements[index] = element;
        // 重新设置引用关系
        setArray(newElements);
    } finally {
        //释放锁
        lock.unlock();
    }
}

添加操作其实很简单:
1、加锁
2、拷贝原数组数据到新数组,空出需要添加元素的位置(末尾或者其它位置)
3、设置原数组引用指向新数组

删除操作

1、remove(int index) 移除某个位置上的数据

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) // 删除的是最后一个元素
            //copy 生成新数组,并重新设置引用关系
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            //copy 元素到新的数组
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        //返回需要删除的数据
        return oldValue;
    } finally {
        lock.unlock();
    }
}

2、remove(Object o) 移除某个元素

public boolean remove(Object o) {
    //存储数据的数组
    Object[] snapshot = getArray();
    //寻找元素o的位置
    int index = indexOf(o, snapshot, 0, snapshot.length);
    //找到了就进行remove 操作CopyOnWriteArrayList 
    return (index < 0) ? false : remove(o, snapshot, index);
}
private boolean remove(Object o, Object[] snapshot, int index) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        // 如果不相等,说明被其它线程修改了数据,重新产生了新的数组
        // 调用remove 之前没有加锁控制,所有其他线程可能修改了数据
        if (snapshot != current) findIndex: {
            int prefix = Math.min(index, len);
            //遍历新的数组,看是否存在元素o
            for (int i = 0; i < prefix; i++) {
                //current[i] != snapshot[i] 数组变了,其元素内存地址也应不一样
                if (current[i] != snapshot[i] && eq(o, current[i])) {
                    index = i; 
                    break findIndex; // 找到了退出
                }
            }
            //如果index >=len 则说明现在的数组缩减了,同时所有数据中,不存在o元素,返回false
            if (index >= len)
                return false;
            //删除的元素就在数组中,引用一样    
            if (current[index] == o)
                break findIndex;
            //还没有找到,从index-len之间再次寻找需要删除的元素o    
            index = indexOf(o, current, index, len);
            //没找到,返回false
            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;
    } finally {
        lock.unlock();
    }
}

总结

CopyOnWriteArrayList 线程安全,读写分离,读取数据时无需同步控制,修改数据时,会进行单独拷贝数组元素进行操作,当操作完成后,在设置新的数组引用。
CopyOnWriteArrayList允许存储的数据为空,允许重复数据,并且元素有序(存储的顺序和添加的顺序一致)
CopyOnWriteArrayList 底层数据结构是一个Object[],初始容量为0,之后每增加一个元素,容量+1,数组复制一遍。
因为每次调整CopyOnWriteArrayList 的结构,都会涉及到数组的拷贝复制,因此比较适合数据量不是很大,读多写少的并发环境。
CopyOnWriteArrayList 读写分离,CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性,当其他线程正在修改数据时,因为是另一份数据的拷贝,因此此时读到的数据可能是脏数据。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值