锁性能提升——CopyOnWriteArrayList

在说CopyOnWriteArrayList之前先说下ArrayList为什么不是线程安全的,通常意义上的线程安全是指在多个线程同时执行的 情况下不会出现数据不一致或者脏数据等,也就是多个线程一起执行与一个线程执行多次的结果应该是一样的。所以其实如果一个操作如果不是原子操作,又没有做线程安全的处理(如锁),那就会有线程安全问题。
比如我们看ArrayList的add方法:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;       (1)
    size = s + 1;                   (2)
}

从代码可以看出是先添加数据,再更新size,所以如果线程1执行到了(1),但是还没执行(2),这时线程2又add,那就会出现数据丢失。

与ArrayList对应的线程安全类有Vector,但是Vector是对add、get、remove等方法添加了synchronized同步保证的线程安全,所以性能较低。

在实际的应用场景中读操作可能远远多于写操作,所以去除get操作的同步开销可以提升性能,但是这时就要考虑到如果去除了get操作的同步,但是由于集合的fail-fast机制,如果去除了get操作的同步,如果一个线程在get,另一个线程add的话就会抛出ConcurrentModificationException异常。

CopyOnWriteArrayList使用了CopyOnWrite的思想,在add或者remove数据的时候,先复制一份数组的副本,对副本进行add操作,然后再将实际数组的引用指向副本,完成更新:

  public boolean add(E e) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

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

 public E remove(int index) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = elementAt(elements, index);
        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;
    }
}

可以发现CopyOnWrite操作的复制行为保证了在进行写操作时,可以并发的进行读操作并且不会有并发问题。而写操作本身是加锁的,所以写操作也不会有并发问题

但是CopyOnWriteArrayList这样实现就有弱一致性的缺点,它只能保证最终一致性,因为线程读取到的可能不是最新的数据。

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,636评论 1赞 14
  •   一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺...
    OmaiMoon阅读 1,849评论 0赞 12
  • jdk版本:1.8.0_77参考文档:jdk 1.8 docs CopyOnWriteArrayList类图 Co...
    小程有话说阅读 750评论 0赞 3
  • 我见过太多的分分合合,总得来说,我分成了三种。第一种,就是比较狗血的剧情,女的一哭二闹三上吊,男的满脸无情。...
    時鈅阅读 698评论 6赞 9
  • 心灵的拷问 1.收起你的不甘 当别人因圆满完成一件事情而欢呼雀跃的时候,你是否会心有不甘?可你是否是问过自己,如果...
    zxj简张阅读 308评论 0赞 0

友情链接更多精彩内容