《Java并发编程实战》-7

线程池的使用


8.1 在任务与执行策略之间的隐形耦合

并非所有的任务有些任务需要明确地指明执行策略,包括:

  • 依赖性任务
  • 使用线程封闭机制的任务。
  • 对应时间敏感的任务。
  • 使用ThreadLocal的任务

在一些任务中,需要拥有或排除某种特定的执行策略。如果某些任务依赖于其他的任务,那么会要求线程池足够大,从而确保它们依赖任务不会被放入等待队列中或者被拒绝,而采用线程封闭机制的任务需要串行执行。通过将这些需求写入文档,将来的代码维护人员就不会由于使用了某种不合适的执行策略而破坏安全性或活跃性。

8.1.1 线程饥饿死锁

每当提交了一个有依赖性的Executor任务时,要清楚地知道可能会出现线程“饥饿”死锁,因此需要在代码或配置Executor的配置文件中记录线程池的大小限制或配置限制。

8.1.2 运行时间较长的任务

如果任务阻塞的时间过长,那么即使不出现死锁,线程池的响应性也会变得糟糕。

定任务等待资源得时间,而不是无限制地等待可以缓解执行时间较长得任务造成的影响。

8.2 设置线程池得大小

一般情况下正确地设置线程池的大小(假设有n个CPU):

  • 计算密集型: 2n+1
  • I/O密集型:2n

8.3 配置ThreadPoolExecutor

ThreadPoolExecutor的通用构造函数

public ThreadPoolExecutor(int corePoolSize,
                        int maximumPollSize,
                        long keepAliveTime,
                        BlockingQueue workQueue,
                        ThreadFactory threadFactory,
                        RejectedExecutionHandler handler)
                        { ... }

线程的创建与销毁

基本大小(Core Poll Size)是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超过这个数量的线程。

线程池的最大大小(maximumPoolSize)表示可同时活动的线程数量的上限。如果某个线程的空闲事件超过了存活时间,那么将标记为可回收,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。

管理队列任务

ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:无界队列、有界队列和同步移交(Synchronous Handoff)。

对于Executor,newCachedThreadPool工厂方法时一种很好的默认选择,他能提供比固定大小的线程更好的排队性能.当需要限制当前任务的数量以满足资源管理需求时,那么可以选择固定大小的线程池,就像在接受网络客户请求的服务器应用程序中,如果不进行限制,那么很容易发生过载问题。

8.3.3 饱和策略

每种RejectedExecutionHandler的实现包含有不用的饱和策略:

  • Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
  • CallerRunPolicy 为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。
  • Discard策略:新提交的任务被抛弃。
    DiscardOldest策略:队列的是“队头”的任务,然后尝试提交新的任务。(不适合工作队列为优先队列场景)。

8.3.4 线程工厂

8.4 扩展ThreadPoolExecutor

实例:给线程池添加统计信息

8.5 递归算法的并行化

实例:谜题框架

对于并发执行的任务,EXecutor框架是一种强大且灵活的框架。它提供了大量可调节的选项,例如创建线程和关闭线程的策略,处理队列任务的策略,处理过多任务的策略,并且提供了几个钩子方法来扩展它的行为。然而,与大多数功能强大的框架一样,其中有些设置参数并不能很好地工作,某些类型的任务需要特定的执行策略,而一些参数组合则可能产生奇怪的结果。

你可能感兴趣的:(读书笔记)