java.util.concurrent包中有几个类适用于常见的多线程通讯。这几个协作类适用范围几乎涵盖了使用wait/notify和Condition最常见的场景,而且更安全、更易于使用。
CyclicBarrier
在CyclicBarrier初始化的时候指定参与者的数量。参与者调用awart()方法进入阻塞状态直到参与者的个数达到指定数量,此时最后一个到达的线程执行预定的屏障任务,然后释放所有的线程。屏障可以被重复的重置状态。常用于协调分组的线程的启动和停止。
CountDownLatch
需要指定一个计数才能初始化CountDownLatch。线程调用await()方法进入等待状态知道计数变为0。其他的线程(或者同一个线程)调用countDown()来减少计数。如果计数变为0后是无法被重置的。常用于当确定数目的操作完成后,触发数量不定的线程。
Semaphore
Semaphore维护一个“许可”集,能够使用acquire()方法检测这个“许可”集,在“许可”可用之前Semaphore会阻塞每个acquire访问。线程能够调用release()来返回一个许可。当Semaphore只有一个“许可”的时候,可当做一个互斥锁来使用。
Exchanger
线程在Exchanger的exchange()方法上进行交互、原子操作的方式交换数据。功能类似于数据可以双向传递的SynchronousQueue加强版。
很多java并发程序需要一个线程池来执行队列中的任务。在java.util.concurrent包中为这种类型的任务管理提供了一种可靠的基本方法。
Executor和易扩展的ExecutorService接口规定了用于执行任务的组件的标准。这些接口的使用者可以通过一个标准的接口使用各种具有不同行为的实现类。
最通用的Executor接口只能访问这种类型的可执行(Runnable)任务 :
void execute(Runnable command)
Executor子接口ExecutorService新加了方法,能够执行:Runnable任务、Callable任务以及任务集合。
Future<?> submit(Runnable task)
Future<T> submit(Callable<T> task)
Future<T> submit(Runnable task, T result)
List<Future<T>> invokeAll (Collection<? extends Callable<T>> tasks)
List<Future<T>> invokeAll (Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
T invokeAny(Collection<? extends Callable<T>> tasks)
T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
Callable类似于Runnable,而且能够返回值、抛出异常:
l V call() throws Exception;
在一个任务执行框架中提交一个Callable 任务,然后返回一个Future结果是很常见的。Future表示在将来的某个时刻能够获取到结果。Future提供能够获取结果或者阻塞直到结果可用的方法。任务运行之前或正在运行的时候,可以通过Future中的方法取消。
如果只是需要一个Runnable特性的Future(例如在Executor执行),可用使用FutureTask。FutureTask实现了Future和Runnable接口,可用提交一个Runnable类型任务,然后在调用部分使用这个Future类型的任务。
ExecutorService最主要的实现类是ThreadPoolExecutor。这个实现类提供了大量的可配置特性:
l 线程池--设定常用线程数量(启动前可选参数)和最大可用线程数量。
l 线程工厂--通过自定义的线程工厂生成线程,例如生成自定义线程名的线程。
l 工作队列--指定队列的实现类,实现类必须是阻塞的、可以是无界的或有界的。
l 被拒绝的任务--当队列已经满了或者是执行者不可用,需要为这些情况指定解决策略。
l 生命周期中的钩子--重写扩展在任务运行之前或之后的生命周期中的关键点
l 关闭--停止已接受的任务,等待正在运行的任务完成后,关闭ThreadPoolExecutor。
ScheduledThreadPoolExecutor是ThreadPoolExecutor的一个子类,能够按照定时的方式完成任务(而不是FIFO方式)。在java.util.Timer不是足够完善的情况下,ScheduleThreadPoolExecutor具有强大的可适用性。
Executors类有很多静态方法(表10)用于创建适用于各种常见情况的预先包装的ExecutorService和ScheduleExecutorService实例
表10
方法 |
描述 |
newSingleThreadExecutor |
创建只有一个线程的ExecutorService |
newFixedThreadPool |
返回拥有固定数量线程的ExecutorService |
newCachedThreadPool |
返回一个线程数量可变的ExecutorService |
newSingleThreadScheduledExecutor |
返回只有一个线程的ScheduledExecutorService |
newScheduledThreadPool |
创建拥有一组核心线程的ScheduledExecutorService |
下面的例子是创建一个固定线程池,然后提交一个长期运行的任务:
在这个示例中提交任务到executor之后,代码没有阻塞而是立即返回。在代码的最后一行调用get()方法会阻塞直到有结果返回。
ExecutorService几乎涵盖了所有应该创建线程对象或线程池的情景。在代码中需要直接创建一个线程的时候,可以考虑通过Executor工厂创建的ExecutorService能否实现相同的目标;这样做经常更简单、更灵活。
7.4、CompletionService
除了常见的线程池和输入队列模式,还有一种常见的情况:为后面的处理,每个任务生成的结果必须积累下来。CompletionService接口允许提交Callable和Runnable任务,而且还可以从任务队列中获取这些结果:(绿色部分和英文版不一样,已和作者确认,英文版将take()和poll()方法混淆了)
l Future<V> take () -- 如果结果存在则获取,否则直接返回
l Future<V> poll () -- 阻塞直到结果可用
l Future<V> poll (long timeout, TimeUnit unit) -- 阻塞直到timeout时间结束
ExecutorCompletionService是CompletionService的标准实现类。在ExecutorCompletionService的构成函数中需要一个Executor,ExecutorCompletionService提供输入队列和线程池。
热门信息:当设置线程池大小的时候,最好是基于当前应用所运行的机器拥有的逻辑处理器的数量。在java中,可用使用Runtime.getRuntime().availableProcessors()获取这个值。在JVM的生命周期中,可用处理器的数目是可变的。
Alex Miller是Terracotta Inc公司Java集群开源产品的技术负责人,曾在BEA System和MetaMatrix工作,是MetaMatrix的首席架构师。他对Java、并发、分布式系统、查询语言和软件设计感兴趣。他的tweeter:@puredanger,blog:http://tect.puredanger.com,很喜欢在用户组会议中发言。在St. Louis,Alex是Lambda Lounge小组的创建人,Lambda Lounge用户组是为了学习、动态语言、Strange Loop开发会议而创建的。
开始阅读英文版的时候,并没有觉得文章中有什么晦涩的地方。但是在翻译之后,才发现将文中的意思清楚地表达出来也是个脑力活,有时一句话能够懂得意思,却是很难用汉语表达出来:“只可意会,不可言传”--这也能解释我当年高中作文为啥每次只能拿40分(总分60)。在禅宗,师傅教弟子佛理,多靠弟子自身的明悟,故有当头棒喝、醍醐灌顶之说。做翻译却不能这样,总不能让读者对着满篇的鸟文去琢磨明悟吧,须得直译、意译并用,梳理文字。
翻译也是一个学习的过程。阅读本文的时候会无意忽略自己以为不重要的词句,待到真正翻译的时候,才发现自己一知半解、一窍不通,就只好Google之,翻译完成后,也学了些知识,可谓是一箭双雕。
个人精力所限,翻译中难免有不对的地方,望大家予以指正。
http://download.csdn.net/source/2805800