JCIP_5_03_Java同步容器

来源:互联网 发布:软件加密锁编程技巧 编辑:程序博客网 时间:2024/06/01 14:37

Java同步容器和并发容器

同步容器

同步容器将所有对容器状态的修改串行化,以实现它们的线程安全性,这种方法的代价是严重降低并发性。当多个线程竞争容器的锁时,吞吐量将严重减低。

同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作,容器上常见的复合操作包括:迭代、跳转以及条件运算。

并发场景下对容器进行迭代,当发现容器在迭代过程中被修改时,就会抛出ConcurrentModificationException异常。

同步容器的问题

如何避免出现ConcurrentModificationException

1.在迭代过程中持有容器的锁

2.“克隆”容器

持有容器锁的问题

某些线程在可以访问容器之前,必须等待迭代过程结束,如果容器规模很大或执行操作的时候很长,那么这些线程将长时间等待。长时间对容器加锁会降低程序的可伸缩性,持有锁的时间越长,那么在锁上的竞争就可能越激烈,如果许多线程都在等待锁被释放,那么将极大地降低吞吐量和cpu的利用率。

“克隆”容器

“克隆”容器并在克隆的副本上进行迭代,由于副本被封闭在线程内,因此其它线程不会在迭代期间对其进行修改,这样就会避免抛出ConcurrentModificationException。克隆容器存在显著的性能开销,这种方式需要考虑多个因素(容器规模、在容器上执行的操作、容器读写频率、对容器响应时间和吞吐量等方面的需求)。

容器的隐式迭代

如果利用加锁或操作副本的方式防止迭代器抛出ConcurrentModificationException,就必须保证所有对共享容器进行迭代的地方都需要加锁,总结比较容易忽略的点:

1.容器的toStringhashCodeequals方法会直接或间接执行迭代,toString方法

2.不同的容器实现所支持的并发操作也是不一样的,如HashTableHashMap

 

代码验证

package org.ybygjy.jcip.chap5;import java.util.Hashtable;import java.util.Iterator;import java.util.Map;/** * 验证并发场景下对Map容器的迭代抛出ConcurrentModificationException异常 * <p>1.早期的并发容器如HashTable,锁的粒度在类实例级别,所有公开的方法共用一个锁</p> * <p>2.</p> * HashTable与HashMap在toString方法中的实现逻辑不同,从HashMap的继承结构来看,HashMap复用了上层AbstractMap的toString方法,而HashTable实现了自己的toString方法。 * HashMap典型Iterator模式所以会有ConcurrentModificationException。 * HashTable也是典型的Iterator模式,但HashTable对外暴露的公共方法是加锁的,如put、toString, * 但唯有entrySet、values方法未直接加锁,这两个方法使用的是同步容器加锁方式,同步容器的作用是为非线程安全的容器增加并发安全的支持(类似代理模式为特定类增加IOC/AOP支持一样)。 * 但在HashTable中的entrySet、values方法调用同步容器工厂对外发布的是新构造的对象,分别是Entry和Collection,但需要注意这两个新实例使用的锁与HashTable实例的锁一致。 * @author WangYanCheng * @version 2014年10月20日 */public class MapConcurrentModificationException {/**实例变量*/private final Map<String, String> containerObj;/** * 构造函数初使化 */public MapConcurrentModificationException() {this.containerObj = new Hashtable<String, String>();//this.containerObj = new HashMap<String, String>();}/** * 测试入口 */public void doWork() {//创建多个线程负责写入for (int i = 0; i < 3; i++) {new Thread(){public void run() {while (true) {String key = String.valueOf(Math.random());containerObj.put(key, key);try {sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();}//创建多个线程负责迭代容器for (int i = 0; i < 10; i++) {Thread thread = new Thread() {public void run() {while (true) {System.out.println(containerObj.toString());//HashTable容器的toString是加锁的,在此通过sleep出让cpu延迟对容器的迭代,这期间写线程会写入新的元素,后续对容器的迭代复现ConcurrentModificationException的机率就更大了。try {sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Iterator<Map.Entry<String, String>> iterator = containerObj.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();System.out.println(entry.getKey() + ":" + entry.getValue());}}}};thread.start();}}/** * 程序入口 * @param args 参数列表 */public static void main(String[] args) {MapConcurrentModificationException mmeInst = new MapConcurrentModificationException();mmeInst.doWork();}}

0 0
原创粉丝点击