博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java CopyOnWriteArrayList的使用
阅读量:6228 次
发布时间:2019-06-21

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

hot3.png

除了加锁外,其实还有一种方式可以防止并发修改异常,这就是将读写分离技术(不是数据库上的)。

先回顾一下一个常识:

1、JAVA中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,几乎不需要CPU时间。

2、JAVA中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。

 基于上面这个常识,我们再来探讨下面这个问题:

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

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

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

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

   在你的应用中有一个列表(List),它被频繁的遍历,但是很少被修改。像“你的主页上的前十个分类,它被频繁的访问,但是每个小时通过Quartz的Job来调度更新”。

如果你使用ArrayList来作为该列表的数据结构并且不使用同步(synchronization),你可能会遇到ConcurrentModificationException,因为在你使用Quartz的Job修改该列表时,其他的代码可能正在遍历该列表。
    有些开发人员可能使用Vector或Collections.synchronizedList(List<T>)的方式来解决该问题。但是这并没有效果!虽然在列表上add(),remove()和get()方法现在对线程是安全的,但遍历时仍然会抛出ConcurrentModificationException!在你遍历在列表时,你需要在该列表上使用同步,同时,在使用Quartz修改它时,也需要使用同步机制。这对性能和可扩展性来说是一个噩梦。同步需要在所有的地方出现,仅仅是因为每个小时都需要做更新。
     幸运的是,这里有更好的解决方案。使用CopyOnWriteArrayList。
当列表上的一个结构修改发生时,一个新的拷贝(copy)就会被创建。这在经常发生修改的地方使用,将会很低效。遍历该列表将不会出现ConcurrentModificationException,因为该列表在遍历时将不会被做任何的修改。
另一种避免添加同步代码但可以避免并发修改问题的方式是在调度任务中构建一个新的列表,然后将原来指向到列表上的引用赋值给新的列表。在JVM中,赋值一个新的引用是原子操作。这种方式在使用旧的遍历方式(for (int i=0; i<list.size(); i++) { … list.get(i) …})时将无效(也会出错)。切换的列表中的大小将引发新的错误产生。更加糟糕的是因为改变是在不同的线程中发生的,所以还会有很多潜在的问题。使用volatile关键字可能会有所帮助,但是对列表大小的改变依然会有问题。
     内存一致性和刚发生后保证了CopyOnWriteArrayList的可用性。同时,代码变得更简单,因为根本不需要使用volatile关键字或同步。更少的代码,更少的bug!
     CopyOnWriteArrayList的另一个使用案例是观察者设计模式。如果事件监听器由多个不同的线程添加和移除,那么使用CopyOnWriteArrayList将会使得正确性和简单性得以保证。

String[] strs = {"111","222","333"};List
list1 = new CopyOnWriteArrayList
(Arrays.asList(strs));List
list2 = new ArrayList
();Iterator
itor1 = list1.iterator(); //写前建迭代器Iterator
itor2 = list2.iterator(); //写前建迭代器list1.add("New");list2.add("New"); try { printAll(itor1); //迭代无误,输入111,222,333} catch (ConcurrentModificationException e) { System.err.println("Shouldn't get here");}try { printAll(itor2); //迭代过程中在itor.next()处抛出异常} catch (ConcurrentModificationException e) { System.err.println("Will get here.");} private static void printAll(Iterator
itor) {while (itor.hasNext()) { System.out.println(itor.next());}

现象解释 写前建迭代器,itor1看到的是111,222,333,而itor2看到的是空,在list1(copyonwrite)修改后,不影响itor1的读过程,照样能把111,222,333读出来,而list2(arraylist)则因为发现expectedCount与modeCount值不匹配而抛出异常。读也是有讲究的,itor1可以总是读出值来(虽然不是最新的),itor2不总是读出值来(但这也能避免值被修改),总之各有各的好吧。

转载于:https://my.oschina.net/swearyd7/blog/269023

你可能感兴趣的文章
ASA防火墙的基本配置
查看>>
[ 1011 ] 有关文件的操作及图说明
查看>>
PXE网络装机
查看>>
人工智能赢了人类,那又如何?
查看>>
今天客户遇到的一个问题:Linux系统的主机碰上了ARP
查看>>
[Thinking In Java]代码整理之移位操作符(shift operators)
查看>>
windows 8预测版本(x64,x86)下载
查看>>
Android Studio 3.0 之后打包apk出现应用未安装问题
查看>>
我的友情链接
查看>>
为你的AliOS Things应用增加自定义cli命令
查看>>
我的友情链接
查看>>
Ez×××客户端在服务器侧没有配置隧道分离的情况下如何直接上公网
查看>>
如何备份cisco路由器配置文件
查看>>
部署Symantec Antivirus 10.0网络防毒服务器之六
查看>>
《paste命令》-linux命令五分钟系列之二十
查看>>
CTO职场解惑指南系列(一)
查看>>
安排!活动素材的亿级用户精准投放
查看>>
debian8.4下配置pgpool+pg9.5双主备
查看>>
用scrapy爬取ttlsa博文相关数据存储至mysql
查看>>
我的友情链接
查看>>