Java并发编程 同步容器


本文整理自《Java并发编程实战》一书。


 

同步容器类包括Vector和Hashtable,二者都是早期JDK的一部分,此外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器类有Collections.synchronizedXxx等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公共方法都进行同步,使得每次都只有一个线程能访问容器的状态。

 

同步容器都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代遍历、跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算,例如“putIfAbsent”。在同步容器类中,这些复合操作在没有客户端加锁的情况下仍然是线程安全的,但当其他线程并发地修改容器时,它们可能会表现出意料之外的行为。

 

如Vector中定义两个方法:getLast 和 deleteLast,它们都会执行“先检查后运行”操作。

public static Object getLast(Vector list){
       intlastIndex = list.size() – 1;
       returnlist.get(lastIndex);
}
public static void deleteLast(Vector list){
       intlastIndex = list.size() – 1;
       list.remove(lastIndex);
}

这些方法看似没有任何问题,从某种程度来看也确实如此----无论多少个线程同时调用它们,也不会破坏Vector,但是从这些方法的调用角度来看,情况就不同了。如果线程A在包含10个元素的Vector上调用getLast,同时线程B在同一个Vector上调用deleteLast,则getLast将抛出ArrayIndexOutOfBoundsException异常。虽然这种情况很好地遵循了Vector的规范----如果请求一个不存在的元素则抛出一个异常,但这并不是getLast的调用者所希望得到的结果。

 

由于同步容器类要遵循同步策略,即支持客户端加锁。因此可以通过同步容器类的自身锁来保护它的每个方法,进而是getLast和deleteLast成为原子操作

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);
}
}

在调用size和相应的get之间,Vector的长度可能会发生变化,如:

for ( int i=0 ; i< vector.size() ; i++ ){
       doSomething(vector.get(i));
}

这种迭代在单线程环境下完全正确,但是在有其他线程并发修改Vector时,则可能导致麻烦。与getLast一样,如果对Vector进行迭代时,另一个线程删除了一个元素,并且这两个操作交替执行,那么这种迭代也会抛出ArrayIndexOutOfBoundsException。虽然这种迭代可能会抛出异常,但这并不意味着Vector就不是线程安全的。Vector的状态仍然是有效的,而抛出的异常也与其规范保持一致。然而,像在抛出异常显然不是人们期望的,也即意料之外的。

 

于是,我们可以通过客户端加锁来解决不可靠迭代的问题,但是要牺牲一些伸缩性,如下代码可以修复上述问题,但是会降低并发性:

synchronized(vector){
       for ( int i=0 ; i<vector.size() ; i++ ){
              doSomething(vector.get(i));
       }
}

 Sun工程师在设计同步容器类的迭代器时并没有考虑并发修改问题,并且它们的表现出的行为是“及时失败 fail-fast”的。这意味着,当它们发现容器在迭代过程中被修改时,就会抛出一个ConcurrentModificationException异常。虽然加锁可以防止迭代器抛出ConcurrentModificationException,但是你必须要记住在所有对共享容器进行迭代的地方都需要加锁。请注意,隐藏迭代器问题,如容器的 toString()、hashCode()、equals()、containsAll()、removeAll()、retainAll()等方法,都会对容器进行迭代。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值