一、同步容器类 1、早期的同步容器类Vector ,Hashtable,类似于Collections.synchronizedXxxx方法创建的,就是用装饰器模式把线程不安全的容器类包装起来,所有公共方法都进行同步,这种容器性能较低。
2、同步容器来的问题(复合操作依然会导致混乱的结果) Vector是线程安全的,只是说明Vector本身不会被并发操作破坏,但这不等于并发操作就没有问题。例如如下两个方法被多线程操作:
public static Object getLast(Vector list){
//第一步
int lastIndex = list.size() - 1;
//第二步
return list.get(lastIndex);
}
public static void deleteLast(Vector list){
//第一步
int lastIndex = list.size() - 1;
//第二步
list.remove(lastIndex);
}
线程A执行第一步完,尚未执行第二步;线程B执行了第一步,再执行第二步完;之后线程A再执行第二步,这个时序就会出现问题集合元素个数变了而线程A不知道,会跑出数组越界异常,因为这些操作不是原子性。通过给被操作容器类加锁可以把这些复合操作变成原子操作(但其实Vector状态没有被破坏):
public static Object getLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
迭代操作也会出现多线程操作导致混乱的问题,在迭代操作上加锁保护起来解决问题,但是降低了并发性。
//可能会被多线程操作而混乱结果,因为迭代是复合操作,不是原子操作
for(int i = 0;i < vector.size();i++){
//TODO
}
//加锁锁住这个vector即可,但是降低了并发性,其他线程不能操作vector
synchronized(vector){
for(int i = 0;i < vector.size();i++){
//TODO
}
}
迭代器与ConcurrentModificationException:即使是同步容器类也没有考虑并发修改问题,对应策略是'fail-fast'快速失败,当发现容器被修改时就抛出异常。改善这种情况可以在迭代时对容器进行克隆,迭代副本。 给容器加锁来进行迭代涉及面太广了,不合适,而且有些迭代器操作是隐藏的,容易被忽视。
public class HiddenIteratro {
@GuardedBy("this")
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) { set.add(i); }
public synchronized void remove(Integer i) { set.remove(i); }
public void addTenThings(){
Random r = new Random();
for(int i=0;i<10;i++)
add(r.nextInt());
System.out.println("DEBUG: added ten elements to " + set);
}
}
容器的hashCode和equals方法,将容器整体放入其他容器,containsAll、removeAll、retainAll等等方法,以及把容器作为参数的构造函数,都会隐含对容器进行迭代,这些迭代都有可能抛出ConcurrentModificationException。
二、并发容器 1、ConcurrentHashMap,使用了分段锁,可以支持任意数量的读线程操作Map;支持多个读线程和写入线程操作Map;可以支持一定数量写入线程同时操作Map,迭代操作不会加锁,采用了弱一致性而不是快速失败,提高了并发性和吞吐量。并发Map还提供了一些复合原子操作,比如存在则删除,存在则替换,不存在则插入等等。 2、CopyOnWriteList,CopyOnWriteHashSet:支持并发操作,迭代不会加锁。
三、阻塞队列和生产者消费者模型 生产者消费者模型的阻塞队列实现了可变对象的线程封闭:开始是只有放入对象的生产者线程能访问可变对象,放入之后生产者不再访问可变对象;后面是只有消费者线程可以访问可变对象,实现了可变对象在线程之间安全的传递。
四、阻塞方法或者中断方法 等待I/O操作结束,等待锁,等待Thread.sleep方法中醒来,等待另一个线程的计算结果(Blocked,Waiting,Timed_Waiting)。方法抛出受检查异常InterruptedException,说明该方法是一个阻塞方法,如果这个方法被中断,它将努力提前结束阻塞状态。抛出该异常后有两个处理策略: 传递异常; 恢复中断状态。
五、同步工具类 1、闭锁(CountDownLatch):countDown方法递减计数器,await方法等待计数器达到零。 2、FutureTask:也可以用作闭锁,FutureTask有三种状态Waiting to run , running , Completed(完成包括正常结束、由于取消而结束、由于异常而结束)。 3、计数信号量(Counting Semaphore),控制同时访问某个特定资源的操作数量,还可用来实现某种资源池,或者对容器施加边界。