并发基础模块

JAVA提供了很多并发的基础模块,来帮助我们构建线程安全的类。这里做一下简要的介绍。帮助构建一个并发模块框架。

同步容器类

早起的Vector和HashTable都是同步容器。同步容器类,通过将它们的状态封装起来,并对每个公有方法都进行同步,使得每次都只有一个线程能访问容器的状态。

同步容器类将并发的操作转换为串行的。当然同步容器是线程安全的。

我们也可以使用同步封装器,Collections.synchronizedXXX方法来创建同步容器。

同步容器存在的问题

同步容器是线程安全的,但是有些必需的符合操作需要客户端加锁来保护。常见的符合操作有:迭代,跳转(根据指定的顺序查找当前元素的下一个元素),条件运算(如:如果存在某元素则把它删除掉)

同步容器的迭代器

对于同步容器来说,如果在迭代过程中,有线程修改了容器中的元素,那么将会导致“及时失败”,将会抛出ConcurrentModificationException。

及时失败,不会做什么操作,只是提醒我们出现了并发错误。

我们可以在迭代时对容器加锁来避免这种异常,但是如果容器很大,迭代加锁可能会导致活跃性问题。

但是对于容器的toString操作也会导致隐性的迭代,有时hashCode或是equals这些操作也会导致隐性的迭代调用。这种情况下,我们很难使用加锁来避免异常。

当然我们也可以使用容器的副本来避免,这就需要根据实际情况来判别了。

并发容器

同步容器将所有的状态访问都串行化,这样会严重的降低并发性,尤其是多个锁同时竞争一个锁,会严重的影响到吞吐量。

从JAVA5开始,增加了Concurrent包,提供了多个支持并发操作的容器。通过使用并发容器来替代同步容器,可以极大的提高伸缩性并降低风险。

如:ConcurrentHashMap就是一个常见的并发容器。它采用了一种叫做分段锁的技术,可以支持支持加载多个锁,保证多个线程同时对容器进行读写操作。所以它的迭代器就不支持及时失败,而是采用了弱一致性,在迭代是允许对容器进行修改,创建时会遍历所有元素,并在迭代器被构造后将修改操作反映给容器。其中也提供了一些额外的原子操作,如:如果没有则添加,若相等则移除等。

对于并发容器size和isEmpty都是估计的,因为数据始终都是在变化中。

阻塞队列和生产者-消费者模式

阻塞队列是JAVA5新增的属性,提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。

put和take方法将阻塞直到达到可以执行状态的发生,而offer和poll不会等待直接返回一个失败。

阻塞队列支持生产者-消费者模式。

类库中BlockingQueue有很多实现。其中LinkedBlockingQueue和ArrayBlockingQueue是FIFO队列,类似于LinkedList和ArrayList,但是具有更强的并发性。PriorityBlockingQueue是一个可以按照优先级排序的队列,可以按照默认的顺序排序也可以自定义比较器(Comparable)。

还存在一个特殊的BlockingQueue--SynchronousQueue,它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同,它维护的是一组线程,这些线程在等待着把元素加入或移出队列。

java6中还新增了双端队列,Deque和BlockingDeque。它们实现了在队列头和尾同时搞笑插入和移除。具体实现有ArrayDeque和LinkedBlockingDeque。它不光支持生产者-消费者模式,还支持工作密取--每个消费者都面对一个专属的生产者,如果对应的生产者队列为空,可以从其他生产者队列的尾部读取数据。

阻塞方法和中断方法

当线程被阻塞时它通常被挂起,并处于某种阻塞状态(BLOCKED、WAITING或TIMED_WAITING)。

Thread提供了interrupt方法,用来中断线程或查询线程是否已经中断。中断是一种协作机制。一个线程不能直接强制终止其他正在执行的线程,只能等待被中断线程自己在适当的时机终止操作。

同步工具类

闭锁

闭锁可以延迟线程的进度直到其到达终止状态。当达到终止状态时,将不会在改变状态。闭锁是一扇门,在闭锁到达结束状态前,所有的线程都无法通过。

例如:魔兽中,只有等所有的玩家到达后才可以开始游戏。

CountDownLatch就是一种灵活的闭锁。存在一个递减的计数器,每个线程对应一个值,当计数器的值减为0以后,表示所有线程都以达到状态。await方法将会阻塞直到计数器值为0。

FutureTask

FutureTask也是一种闭锁。有Callable来实现具体计算,相当于可返回结果的Runnable。它有3中状态,等待运行、正在计算和运行完成。

提供get方法,只有状态为运行完成时,get方法才可以返回结果,否则就会一直阻塞。

信号量

计数信号量可以用来控制同时访问某个特定资源的操作数量,或是执行某个指定操作的数量。

Semaphore管理一组虚拟许可,只有获得许可方可执行,否则就会一直等待直到获得许可。

栅栏

闭锁是一次性对象,一旦进入终止状态就不能被重置。栅栏(Barrier)类似于闭锁,可以阻塞一组线程直到某个事件发生。

栅栏和闭锁的区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的线程反复地在栅栏位置汇集。每个线程都会对应到一个栅栏,每个线程到达栅栏处将会调用栅栏的await方法,来等待其他线程也到达栅栏,等所有线程都到达栅栏后,这些线程才可以继续执行。

Exchanger是一种两方栅栏,各方在栅栏位置上交换数据。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值