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不能保证数据的实时一致性。
CopyOnWriteList揭秘的更多相关文章
- Syscan360会议胸牌破解揭秘
Syscan360会议胸牌破解揭秘 背景 有幸参加今年11月份的上海Syscan360安全会议,会议期间有一个亮点就是360的独角兽团队设计了一款电子badge(胸牌)供参加人员进行破解尝试,类似于美 ...
- CI Weekly #9 | 揭秘阿里 Docker 化实践之路
2017年悄然而至,对 flow.ci 你有什么新的期待呢?新的一年,flow.ci会越来越强大好用,希望继续得到你的支持与反馈.最近,我们做了如下的「功能优化」与「问题修复」,看看有没有你想要的: ...
- js瀑布流 原理实现揭秘 javascript 原生实现
web,js瀑布流揭秘 瀑布流再很久之前流行,可能如我一样入行晚的 ,可能就没有机会去使用.但是这个技术终究是个挺炫酷的东西,花了一个上午来研究,用原生js实现了一个,下面会附上源码,供大家解读. 说 ...
- 【直播】APP全量混淆和瘦身技术揭秘
[直播]APP全量混淆和瘦身技术揭秘 近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,通过逆向手段造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户苦不堪 ...
- angularjs揭秘
angularjs揭秘
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】
通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...
- ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...
- 感恩回馈,新鲜出炉的《ASP.NET MVC 5框架揭秘》免费赠送
上次针对<ASP.NET Web API 2框架揭秘>举办了一次评论赠书活动,很多人问我相同的活动要不要针对<ASP.NET MVC 5框架揭秘>(阅读样章)再来一次,为此我向 ...
随机推荐
- 聊聊docker那些端口问题
今天来系统聊一聊docker的端口,常见的有容器内程序端口.容器端口.主机端口.Dockerfile中EXPOSE端口.docker-compose和docker run中的port等. 貌似很多端口 ...
- Spark词频前十的统计练习
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6815390070254600712/ 承接上一个文档<Spark本地环境实现wordCount单词计数> ...
- Hive分区表和桶表的使用
原文链接: https://www.toutiao.com/i6766897068138037763/?group_id=6766897068138037763 我们看官网文档中这个地方 我们先创建好 ...
- 通过了解Servlet和Http之间的关系,了解web中http通信使用(二)
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6512399401825075719/ 1.<Servlet简单实现开发部署过程> 2.<Serv ...
- Mybatis-Plus的引用
一.依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-b ...
- leetcode 264. 丑数 II 及 313. 超级丑数
264. 丑数 II 题目描述 编写一个程序,找出第 n 个丑数. 丑数就是只包含质因数 2, 3, 5 的正整数. 示例: 输入: n = 10 输出: 12 解释: 1, 2, 3, 4, 5, ...
- CAS学习笔记四:CAS单点登出流程
CAS 的登出包含两种情况,一种是CAS客户端登出,另一种是CAS单点登出,使用流程图说明这两者的不同.(一图胜千言) 总结自官方文档 CAS客户端登出流程 如图,客户端的登出仅仅是过期当前用户与客户 ...
- 【解决了一个小问题】alpine镜像中,busybox的date命令获取昨天的日期
直接上答案: date -d@"$(( `date +%s`-86400))" +"%Y-%m-%d"
- axios请求的封装
/* axios的请求封装 */ //axios的原生写法get,post请求 //第一个参数为请求地址,第二个参数为请求的参数,params是将参数拼接在url的后面 ...
- 海康NVR设备上传人脸图片到人脸库
海康开放平台--海康文档链接--海康开发包和文档下载链接 硬件:海康超脑NVR(全称Network Video Recorder,即网络视频录像机).人脸摄像机. 环境:JDK_1.8 Windows ...