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. centos 操作系统优化

    命令提示符优化 修改PS1环境变化 vim /etc/profile #在最后一行添加 export PS1='[\u@\H \w]$' \u ---显示当前登录用户名称 \h ---显示系统主机名称 ...

  2. Word2010格式化可爱的家乡

    原文链接:https://www.toutiao.com/i6487795632349118990/ 准备样文 选中"可爱的家乡",选择"开始"选项卡,&quo ...

  3. ToDesk-----个人免费 极致流畅的远程协助软件

    ToDesk https://www.todesk.com/ ToDesk官方下载地址 https://www.todesk.com/ 还支持文件传输,用过许多远程的控制工具,这个自我感觉比向日葵好用 ...

  4. Sentry 开发者贡献指南 - Feature Flag

    功能 flag 在 Sentry 的代码库中声明. 对于自托管用户,这些标志然后通过 sentry.conf.py 进行配置. 对于 Sentry 的 SaaS 部署,Flagr 用于在生产中配置标志 ...

  5. 看看CSDN的吃相

    大伙快来看看CSDN的吃相.

  6. 全网最全的Java SpringBoot点赞功能实现

    前言 最近公司在做一个NFT商城的项目,大致就是一个只买卖数字产品的平台,项目中有个需求是用户可以给商品点赞,还需要获取商品的点赞总数,类似下图 起初感觉这功能很好实现,无非就是加个点赞表嘛,后来发现 ...

  7. VS code远程连接Linux 开发C++ 配置详细介绍

    VS code 远程连接服务器,编译C++ 一.前期准备 1.VS code安装 Remote-SSH插件 2.Windows安装SSH. 3.Linux服务器连接测试. a.接通测试使用ping命令 ...

  8. Working hard to know your neighbor's margins:Local descriptor learning loss论文笔记

    Abstract 论文提出了一种新的训练方法,受到了 Lowe's matching criterion for SIFT的启发.这种新的loss,要比负责的正则方法更好.把这个新的loss方法结合L ...

  9. CNN-卷积神经网络简单入门(1)

    卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),是深度学习 ...

  10. Python PyQt5 | Hi音乐 v3.0.0 正式版发布

    Hi音乐 两大平台全音乐搜索.收听与下载的简洁网络音乐播放器 中文介绍 | English Description 源码:Gitee 码云 简介 Hi音乐 是基于 Python 开发的简洁网络音乐播放 ...