首先,java并发容器是线程安全的,但这只是对所使用的并发容器类来说的,对于客户代码,并不总是成立,因为在客户代码中一个原子操作很有可能包含除并发容器之外的其他共享变量,而其他共享变量并不一定是线程安全的,纵使是线程安全的,也不能保证该操作是线程安全的,如下:
import net.jcip.annotations.NotThreadSafe;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@NotThreadSafe
public class VehicleTracker {
//所能监视的最大汽车数量
private int maxCount;
//此处暂时使用Object代表vehicle类
private Map<String,Object> carMap;
public VehicleTracker(int maxCount) {
this.maxCount = maxCount;
carMap=new ConcurrentHashMap<String,Object>(maxCount);
}
public void addVehicle(String id,Object o) throws Exception {
if(carMap.size()<maxCount){
carMap.put(id,o);
}else{
throw new Exception();
}
}
}
此处addVehicle这个原子操作的执行依赖于该类的两个状态变量maxCount与carMap,诚然,carMap的类ConcurrentHashMap是线程安全的,但VehicleTracker这个类却不是线程安全的
想要使他成为线程安全的,在addVehicle方法前加sync关键字看起来是个好主意,但其实根本没有保证该类的线程安全,因为执行该方法的锁与corMap的锁并非同一个锁!办法是
public void addVehicle(String id,Object o) throws Exception {
synchronized (carMap){
//operation...
}
}
使得对maxcount的判断与carMap使用同一个锁;
此种方式确实实现了线程安全,但更巧妙的是使用组合的方式,在此掠过;
至于java并发包给出的几种并发容器类,在设计时并未考虑将Iterator接口也给设计成线程安全的,因此不论是for循环遍历(依赖于容器范围的,该遍历方式必然具有线程安全的问题)以及Iterator遍历(采取的措施是当检测到容器内容被更改时,将会抛出异常,大多数情况下,这不是你希望的),都不可避免的带来线程安全问题;
这个缺陷带来的问题是,当你在 遍历并发容器的方法 前加锁,意味着同时只能有一个线程去遍历,当容器size比较大时,这将会带来很严重的性能问题(除非你只是读取操作,不将其加锁);
问题的解决办法包括使用读写锁