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. Docker_创建自定义镜像(5)

    生成docker镜像有两种方式 使用已有容器生成镜像 使用dockerfile生成镜像 一.使用已有容器生成镜像 1.下载centos镜像,并创建容器 2.进入容器部署python环境 centos镜 ...

  2. gitlab新增ssh

    https://blog.csdn.net/u011925641/article/details/79897517

  3. 安装devstack中遇到的一些问题整理

    1.执行stack.sh文件后提示 ./stack.sh:528:check_path_perm_sanity /opt/devstack/functions:582:die [ERROR] /opt ...

  4. react子组件向父组件传值

    子组件向父组件传值,注意父组件传递函数的时候必须绑定this到当前父组件(handleEmail={this.handleEmail.bind(this)}),不然会报错 /***实现在输入框输入邮箱 ...

  5. Python常用功能函数总结系列

    Python常用功能函数系列总结(一) 常用函数一:获取指定文件夹内所有文件 常用函数二:文件合并 常用函数三:将文件按时间划分 常用函数四:数据去重 Python常用功能函数系列总结(二) 常用函数 ...

  6. axios发送两次请求问题解决

    在使用axios的过程中,会发送两次请求. 看了下是因为有一个请求是OPTIONS来判断跨域的时候让不让发送请求的. 这个不算是一个bug,但是发送两个请求着实让人看着不舒服.于是修改了下,原来的请求 ...

  7. 使用Spring容器动态注册和获取Bean

    有时候需要在运行时动态注册Bean到Spring容器,并根据名称获取注册的Bean.比如我们自己的SAAS架构的系统需要调用ThingsBoard API和Thingsboard交互,就可以通过Thi ...

  8. 【Java常用类】Math

    Math 说明 java.lang.Math提供了一系列静态方法用于科学计算.其方法的参数和返回 值类型一般为double型. 方法 abs 绝对值 acos,asin,atan,cos,sin,ta ...

  9. RT-Thread移植入门学习

    一.简介 RT-Thread 是一款主要由中国开源社区主导开发的开源实时操作系统(许可证GPLv2).实时线程操作系统不仅仅是一个单一的实时操作系统内核,它也是一个完整的应用系统,包含了实时.嵌入式系 ...

  10. 【刷题-LeetCode】221. Maximal Square

    Maximal Square Given a 2D binary matrix filled with 0's and 1's, find the largest square containing ...