Java技术——CopyOnWriteArrayList源码解析

0. 前言  

CopyOnWriteArrayList是一个线程安全,读操作时无锁,但是写操作有锁的ArrayList。是读写分离思想的体现。

实现原理是当某个线程要修改List中的元素时,会把列表中的元素Copy一份,然后在新数组中对元素进行修改,最后把新元素赋值给原来的List的。这样就可以实现读操作不需要加锁

JDK1.5开始Java并发包提供了CopyOnWriteArrayListCopyOnWriteArraySet。这种机制的核心就是使读写分离使并发场景下以读为主的操作性能更高,但显然占用的空间翻了一倍,是一种空间换时间的策略。

 

1.  CopyOnWriteArrayList源码

1.1  CopyOnWriteArrayList初始化

从下面源码中可以看出CopyOnWriteArrayList会创建一个元素数为0的数组,而ArrayList中则是默认为10

底层数据结构array对象数组很明显被volatile关键字修饰,保证内存可见性。

//创建方法
List<String> list = new CopyOnWriteArrayList<String>();

//相关源码
//底层数据结构
private volatile transient Object[] array; 

final Object[] getArray() {
    return array;
}
final void setArray(Object[] a) {
    array = a;
}

//创建了一个个数为0的数组
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

1.2  CopyOnWriteArrayListadd操作

public boolean add(E e) {
     finalReentrantLock lock = this.lock;
     lock.lock();//上锁
     try {
         Object[]elements = getArray();//获取当前的数组
         intlen = elements.length;
         Object[]newElements = Arrays.copyOf(elements, len + 1);
         newElements[len]= e;//将新元素置于新数组的末尾
         setArray(newElements);//将newElements设置为全局array
         returntrue;
        }finally {
           lock.unlock();//解锁
        }
}

根据以上代码可知,CopyOnWriteArrayList每增加一个新元素,都要进行一次数组的复制消耗,所以是一种空间换时间的策略,而且add操作进行了加锁(否则多线程下写操作会出错),并且是对新的数组进行修改remove操作也一样。所以在多线程操作时不会出现java.util.ConcurrentModificationException错误

1.3  CopyOnWriteArrayListget操作

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

从源码可以看出,CopyOnWriteArrayList的读操作没有上锁。因为array数组是volatile修饰的,也就是当你对volatile进行写操作后,会将写过后的array数组强制刷新到主内存,从而在读操作getArray()时会强制从主内存将array读出来。

 

1.4  CopyOnWriteArrayListremove操作

public boolean remove(Object o) {
       final ReentrantLock lock = this.lock;
       lock.lock();
       try {
           Object[] elements = getArray();//获取原数组
           int len = elements.length;//获取原数组长度
           if (len != 0) {
               int newlen = len - 1;//新数组长度为原数组长度-1
               Object[] newElements = new Object[newlen];//创建新数组
 
               for (int i = 0; i < newlen; ++i) {//遍历新数组
                    if (eq(o, elements[i])) {
                        // 将旧数组中将被删除元素之后的元素复制到新数组中
                        for (int k = i + 1; k< len; ++k)
                            newElements[k - 1]= elements[k];
                        setArray(newElements);//将新数组赋给全局array
                        return true;
                    } else
                        newElements[i] =elements[i];//将旧数组中将被删除元素之前的元素复制到新数组中
               }
 
               if (eq(o, elements[newlen])) {//将要删除的元素时旧数组中的最后一个元素
                    setArray(newElements);
                    return true;
               }
           }
           return false;
        }finally {
           lock.unlock();
        }
}

大致流程是上锁后,新建一个比原数组长度-1的新数组,遍历旧数组把除了要删除元素的其他所有元素赋值到新数组中。最后将这个新数组设置给原来的array。这里有个小的点,ArrayListremove使用了System.arraycopy,而这里却没有使用这个方法,所以理论上这里的remove的性能要比ArrayList低。

CopyOnWriteArrayListArrayList的区别

1CopyOnWriteArrayList线程安全,写有锁读无锁,适合多线程中读操作较多的场景。而ArrayList不是线程安全,不能应用在多线程场景中。

2CopyOnWriteArrayList底层数据结构是一个Object[]初始容量为0,每增加一个元素容量加1,而ArrayList的初始容量为10,扩容机制为1.5倍加1

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值