class="MsoNormal" style="line-height: 150%;">8应用线程池
有些类型的任务需要明确指定一个执行策略,如依赖性任务、采用线程限制的任务,对响应时间敏感的任务、使用ThreadLocal的任务。(只有当线程本地thread_local()值的生命周期被限制在当前任务种时,在池的某线程中使用ThreadLocal才有意义;在线程池中,不应该使用ThreadLocal传递任务间的数值)
?
当任务都是同类的、独立时,线程池才有最佳的工作表现。
?
8.1线程饥饿死锁
在线程池中,如果一个任务依赖于其他任务的执行,就可能产生死锁。如:对于一个单线程化的Executor,一个任务将另一个任务提交到相同的Executor中,并等待提交的任务的结果,总会引起死锁。
第二个任务滞留在工作队列中,直到第一个任务完成;但第一个任务不会完成,因为它在等待第二个任务的完成。——这就是线程饥饿死锁。
?
因此,无论何时,提交一个非独立的Executor任务时,要明确出现线程饥饿死锁的可能性,且在代码或者配置文件以及其他可以配置Executor的地方,任何有关池的大小和配置约束都要写入文档。
?
8.1.2耗时操作
耗时任务会造成线程地堵塞,还会拖长服务时间。解决之道:限定任务的等待资源的时间。
?
大部分平台类库中的阻塞方法,同时提供了限时、非限时的2个版本,如Thread.join/BlockingQueue.put/CountDownLatch.await/Selector.select
?
8.2定制线程池大小
池的长度应该由某种配置机制来提供,不要硬编码,当然无须特别准确,只要以防过大或过小。
?
8.3配置ThreadPoolExcutor
核心池大小(Core Pool SIze)、最大池大小(max Pool Size)、存活时间(Keep Live Time)共同管理线程的创建与销毁。
?
核心池大小(Core Pool SIze)是目标的大小,线程池的实现试图维护池的大小:即没有任务执行,池的大小也等于核心池的大小;且直到工作队列充满时,池都不会创建更多的线程;
?
最大池大小(max Pool Size)是可同时活动的线程数的上限。如果一个线程已经闲置的时间超过了存活时间(Keep Live Time),会成为被回收的候选者,如果当前池的大小超过了核心池的大小,线程池会终止它。
?
Public ThreadPoolExecutor(int CorePoolSize,int maxPoolSize,long keepLiveTime,TimeUnit unit,……)
?
通过调节核心池的大小与存活时间,可以促进线程池归还空闲线程占有的资源。
?
newFixedThreadPool:工厂为请求的池设定了核心池的大小,最大池的大小,永不会超时;
newCachedThreadPool:工厂将最大池大小设置为Integer.MAX_VALUE,核心池大小为0,超时1分钟
?
8.3.2管理队列任务
ThreadPoolExecutor允许提供了一个BlockingQueue来持有等待执行的任务,任务排队有3种基本方法:无限队列,有限队列,同步移交。
?
newFixedThreadPool和newSingleThreadExecutor默认使用的是一个无限的LinkedBlockingQueue,如果所有的工作者线程都处于忙碌状态,任务将会在队列中等候;如果任务持续的快速到达,超过了它们被处理的速度,队列也会无限的增加。
?
稳妥的资源管理策略是使用有线的队列(如ArrayBlockingQueue、有限的LinkedBlockingQueue、PriorityBlockingQueue)
有界队列有助于避免资源耗尽的情况发生,但队列满时,要有相应的“饱和策略”处理。
?
newCachedThreadPool工厂提供了比定长的线程池更好的队列等候性能,它是Executor的一个很好的默认选择。
?
8.3.3饱和策略
当一个有线队列充满时,饱和策略开始生效。
ThreadPoolExecutor的饱和策略可以通过调用setRejectedExectionHandler来修改(如果任务提交到一个已经关闭的Executor时,也会用到饱和策略)
?
JDK提供了几种不同的RejectedExectionHandler实现,每一个实现不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy.
?
默认的中止(abort)策略引起Executor抛出异常;调用者捕获异常,编写满足自己需求的处理代码;
当最新提交的任务不能进入队列等候执行时,“遗弃(Discard)”策略会放弃这个任务;
“遗弃最旧的(Discard——oldest)”策略选择丢弃的任务,是本应接下来执行的任务,该策略还会尝试重新提交新任务。(如果工作队列是优先级队列,那么遗弃最旧的策略选择丢弃的是刚好优先级最大的元素,所以混合使用遗弃最旧的饱和策略与优先级不可行)
?
“调用者运行(Call_runs)”策略的实现形式,既不会丢弃哪个任务,也不会抛出异常。它会把一些任务推回到调用者那里,以此缓解新任务流。它不会在线程池中执行最新的任务,但会在一个调用了executor的线程中执行。
?
8.3.4线程工厂
线程池创建线程,要通过一个线程工厂实现。默认的线程工厂创建了一个新的、非后台的线程。
ThreadFactory只有唯一的方法: .newThread()。它会在线程池需要创建一个新线程时调用。
?
8.4拓展ThreadPoolExecutor
它提供了几个“钩子”让子类去覆盖——beforeExecute、afterExecute、terminate——用于拓展ThreadPoolExecutor的行为。
(可以用于添加日志、时序、监视器、统计信息)
?
无论是正常从run返回,还是抛出异常,afterExecute会调用。
Terminate线程池关闭时调用。
?
Terminate可以用于释放Executor在生命周期中分配到的资源,还可以发出通知、记录日志、完成统计信息。
?
当每一个迭代彼此独立,且完成循环时,每个迭代的工作意义都足够大,足以弥补管理一个新任务的开销时,这个顺序循环是适合并行的。
?
9 GUI应用程序
几乎所有的GUI工具集都实现为“单线程化子系统”,意味着所有GUI的活动都被限制在一个单独的线程中,如Swing、SWT。
?
早期GUI:GUI事件在“主事件循环”进行处理;
现代GUI:创建一个专门的线程,事件派发线程EDI来处理GUI事件
(多线程GUI易受死锁影响)
?
单线程化的GUI框架通过线程限制来达到线程安全性,所有GUI中的对象,包括可视化组件和数据模型,都只能被事件线程访问。
?
Swing的线程限制
Swing的单线程规划:Swing的组件和模型只能在事件分派线程中被创建、修改和请求。
?
?