Java同步容器和并发容器
同步容器
同步容器将所有对容器状态的修改串行化,以实现它们的线程安全性,这种方法的代价是严重降低并发性。当多个线程竞争容器的锁时,吞吐量将严重减低。
同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作,容器上常见的复合操作包括:迭代、跳转以及条件运算。
并发场景下对容器进行迭代,当发现容器在迭代过程中被修改时,就会抛出ConcurrentModificationException异常。
同步容器的问题
如何避免出现ConcurrentModificationException
1.在迭代过程中持有容器的锁
2.“克隆”容器
持有容器锁的问题
某些线程在可以访问容器之前,必须等待迭代过程结束,如果容器规模很大或执行操作的时候很长,那么这些线程将长时间等待。长时间对容器加锁会降低程序的可伸缩性,持有锁的时间越长,那么在锁上的竞争就可能越激烈,如果许多线程都在等待锁被释放,那么将极大地降低吞吐量和cpu的利用率。
“克隆”容器
“克隆”容器并在克隆的副本上进行迭代,由于副本被封闭在线程内,因此其它线程不会在迭代期间对其进行修改,这样就会避免抛出ConcurrentModificationException。克隆容器存在显著的性能开销,这种方式需要考虑多个因素(容器规模、在容器上执行的操作、容器读写频率、对容器响应时间和吞吐量等方面的需求)。
容器的隐式迭代
如果利用加锁或操作副本的方式防止迭代器抛出ConcurrentModificationException,就必须保证所有对共享容器进行迭代的地方都需要加锁,总结比较容易忽略的点:
1.容器的toString、hashCode、equals方法会直接或间接执行迭代,toString方法
2.不同的容器实现所支持的并发操作也是不一样的,如HashTable与HashMap。
代码验证
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();
}
}