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不能保证数据的实时一致性。

CopyOnWriteList揭秘的更多相关文章

  1. Syscan360会议胸牌破解揭秘

    Syscan360会议胸牌破解揭秘 背景 有幸参加今年11月份的上海Syscan360安全会议,会议期间有一个亮点就是360的独角兽团队设计了一款电子badge(胸牌)供参加人员进行破解尝试,类似于美 ...

  2. CI Weekly #9 | 揭秘阿里 Docker 化实践之路

    2017年悄然而至,对 flow.ci 你有什么新的期待呢?新的一年,flow.ci会越来越强大好用,希望继续得到你的支持与反馈.最近,我们做了如下的「功能优化」与「问题修复」,看看有没有你想要的: ...

  3. js瀑布流 原理实现揭秘 javascript 原生实现

    web,js瀑布流揭秘 瀑布流再很久之前流行,可能如我一样入行晚的 ,可能就没有机会去使用.但是这个技术终究是个挺炫酷的东西,花了一个上午来研究,用原生js实现了一个,下面会附上源码,供大家解读. 说 ...

  4. 【直播】APP全量混淆和瘦身技术揭秘

    [直播]APP全量混淆和瘦身技术揭秘 近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,通过逆向手段造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户苦不堪 ...

  5. angularjs揭秘

    angularjs揭秘

  6. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

    本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...

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

    通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...

  8. ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

    到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...

  9. 感恩回馈,新鲜出炉的《ASP.NET MVC 5框架揭秘》免费赠送

    上次针对<ASP.NET Web API 2框架揭秘>举办了一次评论赠书活动,很多人问我相同的活动要不要针对<ASP.NET MVC 5框架揭秘>(阅读样章)再来一次,为此我向 ...

随机推荐

  1. webSocket 前端 js 加入 心跳机制 的基本写法

    1前言 websocket 一般 每隔 90 秒无操作则会自动断开  ,需要加入一个心跳机制 来防止 自断 2. 实验过程 (1)设定一个jsp 或html 文件都行 ,加入元素 (2)js 源码 , ...

  2. vue 使用mock来模拟数据

    首先,需要安装mock npm install mockjs --save-dev 在main.js中引入mock mock文件写法如下: const Mock = require('mockjs') ...

  3. JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)

    JavaScript的执行过程 前言 编写一段JavaScript代码,它是如何执行的呢?简单来说,JS引擎在执行JavaScript代码的过程中需要先解析再执行.那么在解析阶段JS引擎又会进行哪些操 ...

  4. 智能集成接口:I3 ISA-95 的应用

    介绍 多年来,使用基于制造运营管理 (MOM) 的应用程序的制造 IT 顾问试图说服制造商这些类型的应用的高价值.实时 MOM 解决方案是唯一一组能够精确优化工厂日常运营的 IT 应用程序,可为其可用 ...

  5. Linux中的一些基本命令

    文章目录 ls cd Linux的目录 文件的权限 1.用户,组,权限 2.文件的权限 文件的基本操作 增:创建文件 删:删除文件 改:修改文件 查:查看 vi/vim 是一个编辑工具,主要用来编辑文 ...

  6. Vue框架怎么使用mediainfo.js来检测视频文件是否有音轨?

    啥是MediaInfo? 简而言之,MediaInfo是一个能读取音频和视频文件并分析和输出音视频信息的工具,能输出的内容包括视频信息,音轨,字幕等. MediaInfo也可以在web端使用,需要使用 ...

  7. C++多线程之互斥锁和超时锁

    #include<iostream> #include<thread> #include<mutex> using namespace std; mutex mu; ...

  8. Linux 标准输入输出、重定向

    一 相关知识 1)默认地,标准的输入为键盘,但是也可以来自文件或管道(pipe |). 2)默认地,标准的输出为终端(terminal),但是也可以重定向到文件,管道或后引号(backquotes ` ...

  9. Python 单元测试 生产HTML测试报告

    使用HTMLTestRunnerNew模块,生成单元测试的html报告,报告标题根据对应测试时间. import unittest from datetime import datetime from ...

  10. Task+ConcurrentQueue多线程编程

    队列(Queue)代表了一个先进先出的对象集合.当您需要对各项进行先进先出的访问时,则使用队列.当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队. ConcurrentQueue< ...