多线程系列提高(4)--同步容器类

一、同步容器类

同步容器类包括Vector和HashTable,此外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器类是由Collections.synchronizedXxx等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。
(1)同步容器类的问题
同步容器类是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代(反复访问元素,直到遍历完容器中所有元素)、跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算,例如:若“没有则添加”(检查在Map中是否存在键值K,如果没有,就加入键值对(K,V))。在同步容器类中,这些复合操作在没有呀客户端加锁的情况下仍然是线程安全的,但当其它线程并发地修改容器时,它们可能会出现意料之外的行为。

  //在使用客户端加锁的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);
        }
    }

上述程序在Vector中定义了两个方法:getLat和deleteLast,如果上面没有加同步锁,当从调用者的角度来看,如果线程A在包含10个元素的Vector上调用getLast,同时线程B在同一个Vector上调用deleteLast,这些操作交替执行时,getLast将抛出ArrayIndexOutOfBoundsException异常,在调用size与调用getLast这两个操作之间,Vector变小了,因此在调用size时得到的索引值将不再有效,此时如果请求一个不存在的元素,那么将抛出一个异常。
此时从调用者的角度,从客户端加锁,保证使用这些方法时也是一个原子操作。即通过获得容器类的锁,我们可以使得getLast和deleteLast成为原子操作,并确保Vector的大小在调用size和get之间不会发生变化。

(2)迭代器与ConcurrentModificationException(并发修改异常)
无论是直接迭代还是在Java 5.0引入的for-each循环语法中,对容器类进行迭代的标准方式都是使用Iterator。然而,如果有其它线程并发的修改容器,那么即使是使用迭代器也无法避免在迭代期间对容器加锁。在设计同步容器类的迭代器时并没有考虑到并发修改的问题,并且它们表现出的行为是“及时失败”(fail-fast)的,这意味着,当它们发现容器在迭代过程中被修改时,就会抛出一个ConcurrentModificationException异常。
这种“及时失败”的迭代器作为一种“善意的”捕获并发错误,因此只能作为并发问题的预警指示器。它们采用的方式是:将计数器的变化与容器关联起来,如果在迭代期间计数器被修改,那么hasNext或next将抛出ConcurrentModificationException。,然而这种检查是在没有同步的情况下进行的,因此可能看到失效的计数值,而迭代器可能并没有意识到已经发生了修改,这是一种设计上的平衡,从而降低并发修改操作的检测代码对程序性能带来的影响。在单线程代码中也可能会出现ConeurrentModificationException异常,当对象直接从容器中删除而不是通过Iterator.remove来删除时,就会抛出这个异常。

二、并发容器

JDK5.0提供了多种并发容器类来改进同步容器的性能。同步容器将所有对容器状态的访问都串行化,以实现它们的线程安全性。这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重减低。
并发容器是针对多个线程并发访问设计的。在JDK5.0中增加了以下几种容器类型:
ConcurrentHashMap:用来替代同步且基于散列的Map;
CopyOnWriteArrayList:用于在遍历操作为主要操作的情况下代替同步的List。
Queue:用来临时保存一组等待处理的元素。它提供了几种实现,包括:ConcurrentLinkedQueue,这是一个传统的先进先出队列,以及PriorityQueue,这是一个(非并发的)优先队列。Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值,虽然可以用List来模拟Queue的行为–事实上,正是通过LinkedList来实现Queue的,但还需要一个Queue的类,因为它能去掉List的随机访问需求,从而实现更高效的并发。

BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,直到队列中出现一个可用的元素。如果队列已满(对于有界队列来说),那么插入元素的操作将一直阻塞,直到队列中出现可用的空间。在生产者消费者设计模式中,阻塞队列是非常有用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值