博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CopyOnWriteArrayList 的set为什么要复制?扩容为什么一个一个来,而不是1.5倍
阅读量:4082 次
发布时间:2019-05-25

本文共 3040 字,大约阅读时间需要 10 分钟。

扩容可以理解,set为什么要复制?

参考:

ArrayList 的一个线程安全的变体,其中所有可变操作(添加、设置,等等)都是通过对基础数组进行一次新的复制来实现的。 

这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内绝不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。自创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。不支持迭代器上更改元素的操作(移除、设置和添加)。这些方法将抛出 UnsupportedOperationException。 

针对iterator使用了一个叫 COWIterator的阉割版迭代器,因为不支持写操作,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前 引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全

这个思想在我此前这篇文章   

hashmap与hashtable区别

 里也有涉及,HashMap的迭代器(Iterator)是fail-fast迭代器,所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException

————————————————————————————————————

另一篇文章:http://blog.csdn.net/hzzhoushaoyu/article/details/26146313

然后是这里为什么要将引用指向clone的一个新对象

对于在set之前得到指向老对象引用的不进行干扰,包括getArray和iterator等,而且CopyOnWriteArrayList在大多为读操作时才使用,写操作性能较差。发布一个不可变对象,是不需要进一步的同步操作这是CopyOnWrite的核心思路。

作者不仅提到了迭代器的安全,还提到了不可变对象在读操作时的“0同步处理需求”

同样的,还有String

************************************

(二)再回来看第2个问题:扩容为什么不直接1.5倍的扩,像ArrayList一样

先来看ArrayList为什么要1.5倍的扩,因为下次省的再扩,而copyonwritearraylist不一样,为了使迭代器达到最安全的状态,每次都复制,直接扩大哦1.5倍没有意义

参考原文:

由于之前在弄 eventbus的源码分析时,源码中有用到CopyOnWriteArray 由于时间问题,知道现在才做了一个相应的整理:

---------------------------------------------------------做一枚健康的小码畜--------------------------------------------------

CopyOnWriteArrayList是 的一个线程安全的变体(ArrayList不是线程安全的哦),其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

在源码中可以看到:

1 public CopyOnWriteArrayList(Collection
c) {2 Object[] elements = c.toArray();3 // c.toArray might (incorrectly) not return Object[] (see 6260652)4 if (elements.getClass() != Object[].class) //这里是copyof,5 elements = Arrays.copyOf(elements, elements.length, Object[].class);6 setArray(elements);7 }

CopyOnWriteArrayList 其实是做了一份拷贝,所以一般需要很大的开销,在android中不建议将context或者activity放到CopyOnWriteArrayList中

 尽管需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。允许使用所有元素,包括null。

在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过JDK1.6的Arrays.copyof()来生成一份新的数组

然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象,这样保证了每次写都是在新的对象上(因为要保证写的一致性,这里要对各种写操作要加一把锁,JDK1.6在这里用了重入锁),

然后读的时候就是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞,针对iterator使用了一个叫 COWIterator的阉割版迭代器,因为不支持写操作,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前 引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全。

CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之 间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的“=”将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合 在多线程里使用,绝对不会发生ConcurrentModificationException ,所以最后得出结论:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。

    内存一致性效果:当存在其他并发 collection 时,将对象放入CopyOnWriteArrayList之前的线程中的操作  随后通过另一线程从CopyOnWriteArrayList中访问或移除该元素的操作。 

   这种情况一般在多线程操作时,一个线程对list进行修改。一个线程对list进行fore时会出现java.util.ConcurrentModificationException错误。

   比如:两个线程一个线程fore一个线程修改list的值。

你可能感兴趣的文章
标定之相机的内参和外参
查看>>
刚刚发现《OpenCV4快速入门》里面有讲到pnp
查看>>
我忽然想到,标定的原理是不是就是利用的pnp?
查看>>
ROS_PACKAGE_PATH总结一下到底什么时候要添加,还有Python写的功能包怎么添加环境变量?
查看>>
通过ROS开启Gazebo的世界(这篇写得不错,教会了我怎么自己建一个gazebo的功能包,建立世界模型,导入机器人模型,通过roslaunch启动,值得细读真正学会!)
查看>>
ROS摄像机的标定(这里很好的一点就是给出了标定结果的各个参数的含义,这个很多都没讲)
查看>>
实际好像只用到了相机内参矩阵和畸变参数?
查看>>
计算机视觉-相机内参数和外参数
查看>>
相机内参和外参的解释
查看>>
我感觉我自己基于pnp原理可以自己写个简单的SLAM了,而且还是VIO的。是的,可以自己推出。
查看>>
菜鸟专学:从头到尾创建自己的SLAM系统(转载,我觉这种自己写一个SLAM很好)
查看>>
C++之STL和Boost
查看>>
kubernetes节点与节点之间的通信是如何弄的?我想和ROS的对比一下
查看>>
kubernetes中文文档
查看>>
K8S 容器之间通讯方式
查看>>
我看Kubernetes的操作不需要图形界面,只需要命令行就可以了,所以买个云服务器就可以弄了应该。
查看>>
现在来看,做个普罗米修斯的docker镜像对我而言并不难,对PX4仿真环境配置也熟悉了。
查看>>
删除docker容器和镜像的命令
查看>>
Docker run 命令
查看>>
Docker容器的创建、启动、和停止
查看>>