CopyOnWriteList揭秘

List的并发容器-CopyOnWriteList

Vector和SynchronizedList

ArrayList是用来代替Vector,Vector是线程安全的容器,因为它在方法上都加上了synchronized同步关键字

例如:

public synchronized void copyInto(Object[] anArray) {
        System.arraycopy(elementData, 0, anArray, 0, elementCount);
}

/**
 * Trims the capacity of this vector to be the vector's current
 * size. If the capacity of this vector is larger than its current
 * size, then the capacity is changed to equal the size by replacing
 * its internal data array, kept in the field {@code elementData},
 * with a smaller one. An application can use this operation to
 * minimize the storage of a vector.
 */
public synchronized void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (elementCount < oldCapacity) {
        elementData = Arrays.copyOf(elementData, elementCount);
    }
}

而Collections.synchronizedList方法也是在方法内部加了synchronized关键字

问题

public static void main(String[] args) {
    Vector vector = new Vector();
    vector.add("a");
    vector.add("b");
    vector.add("c");
    new Thread(()->{
        getLast(vector);
    }).start();
    new Thread(()->{
        removeLast(vector);
    }).start();

    new Thread(()->{
        getLast(vector);
    }).start();
    new Thread(()->{
        removeLast(vector);
    }).start();
}

private static void removeLast(Vector vector) {
    int index  = vector.size() - 1;
    vector.remove(index);
}

private static Object getLast(Vector vector) {
    int index = vector.size() - 1;
    return vector.get(index);
}

以上这样的代码可能会发生异常,线程在交替执行的时候,我们自己方法getLast和removeLast没有保证原子性

要解决以上问题也很简单,就是在我们自己写的方法中做同步处理,例如添加synchronized关键字,想下面示例这样:

private synchronized static void removeLast(Vector vector) {
    int index  = vector.size() - 1;
    vector.remove(index);
}

private synchronized static Object getLast(Vector vector) {
    int index = vector.size() - 1;
    return vector.get(index);
}

再看遍历Vector集合的时候

例如,遍历获取vector.size()为3,当其他线程对容器做了修改后,此时容器的size为2,遍历获取get(3)就会出现异常

如果使用for-each(迭代器)来做上面的操作,会抛出ConcurrentModificationException异常

要解决这个问题,也是在遍历方法对vector加锁

CopyOnWriteList

一般来说,我们会认为:CopyOnWriteArrayList是同步List的替代品,CopyOnWriteArraySet是同步Set的替代品

无论是Hashtable到ConcurrentHashMap,Vector到CopyOnWriteArrayList。
JUC下支持并发的容器与老一代的线程安全类相比,都是在做锁粒度的优化

实现

什么是COW

如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,
直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,
而其他调用者所见到的最初的资源仍然保持不变。
优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。

看看CopyOnWriteArrayList中的数据结构

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

/**
 * Gets the array.  Non-private so as to also be accessible
 * from CopyOnWriteArraySet class.
 */
final Object[] getArray() {
    return array;
}

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

数据结构比起ConcurrentHashMap来说很简单,使用Lock来上锁(修改数据的时候),使用Object数组来保持数据

CopyOnWriteArrayList的特点

  • CopyOnWriteArrayList是线程安全容器(相对于ArrayList),底层通过复制数组的方式来实现。
  • CopyOnWriteArrayList在遍历的使用不会抛出ConcurrentModificationException异常,并且遍历的时候就不用额外加锁
  • 元素可以为null

揭秘

CopyOnWriteList如果做到并发环境下遍历容器而不发生异常呢?

接下来我们看看iterator方法,该方法返回的是COWIterator类。我们可以看看这个类是怎么组成的

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    public boolean hasPrevious() {
        return cursor > 0;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor-1;
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code remove}
     *         is not supported by this iterator.
     */
    public void remove() {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code set}
     *         is not supported by this iterator.
     */
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code add}
     *         is not supported by this iterator.
     */
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        Object[] elements = snapshot;
        final int size = elements.length;
        for (int i = cursor; i < size; i++) {
            @SuppressWarnings("unchecked") E e = (E) elements[i];
            action.accept(e);
        }
        cursor = size;
    }
}

可以看到类中有一个 Object[] snapshot这样的数组,根据代码可以直到这个数组保持的是待遍历的数组,对应的的就是CopyOnWriteArrayList中
保存数据的数组

由上我们可以知道,迭代器中保存的是获取CopyOnWriteList集合迭代器时的数据。所以在迭代过程中修改原来集合的数据不会影响到迭代器的遍历,所以CopyOnWriteList不能保证数据的实时一致性。

原文地址:https://www.cnblogs.com/watertreestar/p/11780254.html

时间: 2024-08-30 17:32:51

CopyOnWriteList揭秘的相关文章

一起来造一个RxJava,揭秘RxJava的实现原理

RxJava是一个神奇的框架,用法很简单,但内部实现有点复杂,代码逻辑有点绕.我读源码时,确实有点似懂非懂的感觉.网上关于RxJava源码分析的文章,源码贴了一大堆,代码逻辑绕来绕去的,让人看得云里雾里的.既然用拆轮子的方式来分析源码比较难啃,不如换种方式,以造轮子的方式,将源码中与性能.兼容性.扩展性有关的代码剔除,留下核心代码带大家揭秘RxJava的实现原理. 什么是响应式编程 首先,我们需要明确,RxJava是Reactive Programming在Java中的一种实现.什么是响应式编程

揭秘淘宝花呗套现黑色产业链

缺钱这事儿谁都遇到过,你选择什么方式解决?在阿里系的花呗.腾讯系的微粒贷.京东系的白条等产品上线后,不少网友打主意要薅他们的羊毛.但随之而来的,却是各种类似"花呗套现被诈骗"的社会新闻.所以,究竟是谁在薅谁的羊毛?本文试图以花呗套现为例,揭秘互联网信贷套现背后的黑色产业链,以及由此涉及的各类风险. 账户强开?妥妥的陷阱 实际上,标题中所提到的"淘宝花呗",实际应称为"蚂蚁花呗",归属于阿里集团的关联公司.蚂蚁金服旗下.暂时选择前者作为称呼,是由于

AlphaGo深度揭秘

今日,在乌镇围棋峰会人工智能高峰论坛上,AlphaGo之父.DeepMind创始人戴密斯·哈萨比斯(Demis Hassabis)和DeepMind首席科学家大卫·席尔瓦(David Silver)在论坛上透露了关于AlphaGo的重要信息,以及AlphaGo究竟意味着什么?让人们能详细了解到AlphaGo背后的秘密. AlphaGo是什么? AlphaGo 是第一个击败人类职业围棋选手并战胜围棋世界冠军的程序,是围棋史上最具实力的选手之一.2016 年 3 月,在全世界超过一亿观众的关注下,A

《css3揭秘》的效果code

1.在阅读css3揭秘的基础上,跟着书中的效果组合起来的这组代码. 2.代码中有四张图片分别是 1.jpg 2.jpg  3.jpg 4.jpg; 作为demo,图片名称没有语义. 3.兼容性:  IE上有部分并不兼容,Edge,FF,chrome没有问题. 4.下面是一个选择器的实现: 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"

[转]C#综合揭秘——细说多线程(上)

C#综合揭秘——细说多线程(上) 引言 本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发.其中委托的BeginInvoke方法以及回调函数最为常用.而 I/O线程可能容易遭到大家的忽略,其实在开发多线程系统,更应该多留意I/O线程的操作.特别是在ASP.NET开发当中,可能更多人只会留意在客户端使用Ajax或者在服务器端使用UpdatePanel.其实合理使用I/O线程在通讯项目或文件下载时,能尽可能地减少IIS的压力.并行编程

大型网站MySQL深度优化揭秘 2

学无止境,老男孩教育成就你人生的起点! 相信自己相信老男孩!!! 老男孩-51cto-公开课-大型网站MySQL深度优化揭秘 部分整理有没跟上的抱歉 ? 目 录 大型网站MySQL深度优化揭秘????2 第1章 优化的思路和线路????2 1.1 网站优化的思路????2 1.2 MySQL优化,nginx这样的东西怎么优化?????2 第2章 硬件层面优化????3 2.1 数据库物理机????3 2.1.1 CPU????3 2.1.2 Memory????3 2.1.3 disk(磁盘IO

揭秘JavaScript中谜一样的this

揭秘JavaScript中谜一样的this 在这篇文章里我想阐明JavaScript中的this,希望对你理解this的工作机制有一些帮助.作为JavaScript程序员学习this对于你的发展有很大帮助,可以说利大于弊.这篇文章的灵感来自于我最近的工作——我即将完成的书的最后章节——JavaScript 应用程序设计(JavaScript Application Design)(注意:现在你可以购买早期版本),我写的是关于scope工作原理的方面. 似是而非,这可能是你对this的感觉: 很疯

ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】

通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最终采用何种方式提供我们所需的服务实例取决于最终选择了怎样的ServiceCallSite,而服务注册是采用的ServiceDescriptor有决定了ServiceCallSite类型的选择.我们将众多不同类型的ServiceCallSite大体分成两组,一组用来创建最终的服务实例,另一类则与生命周

俞敏洪自揭创业伤疤,股权分配过程大揭秘

有同学说,听了老俞二三十场讲座,这场收获最多,因为他揭秘了自己创业过程中的点点教训.确实,励志少,真言多.特别是对“合伙”过程中种种难题的思考,血一般的教训,金子般的箴言. 如果你曾听过新东方的故事,你一定要看这篇文章,因为谁都不知道原来背后他们掐了这么多回,不是互掐,是群掐. 抛几条,你们感受下. 合伙成功的第一个要素:“掌局者” 最好的方法真的是先干一段时间,哪怕你先做一个月.两个月,再把周围朋友拉进来:做的时间越长越好,这是奠定了这公司是你创立的基础.如果要一起干的话,有一个前提条件:你在