JCIP_5_03_Java同步容器

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值