第8章 线程池的使用
在任务与执行策略之间的隐性耦合
有些类型的任务需要明确的指明执行策略:
- 依赖性任务。如果提交线程池的任务需要依赖其他任务,那么就隐含的给执行策略带来了约束,此时应小心的避免产生活跃性问题
- 使用线程封闭机制的任务。单线程的 Executor 能够保证任务不会并发执行
- 对响应时间敏感的任务。
- 使用 ThreadLocal 的任务。线程池的线程中不应该使用 ThreadLocal 在任务之间传递值
只有当任务是相同类型并且相互独立时,线程池的性能才能达到最佳。
线程饥饿死锁
在单线程 Executor 中任务发生死锁
public class ThreadDeaklock {
ExecutorService exec = Executors.newSingleThreadExecutor();
public class RenderPageTask implements Callable<String> {
public String call() throws Exception {
Future<String> header, footer;
header = exec.submit(new LoadFileTask("header.html"));
footer = exec.submit(new LoadFileTask("footer.html"));
String page = renderBody();
// 将发生死锁 -- 由于任务在等待子任务的结果
return header.get() + page + footer.get();
}
}
public void submitTask(){
executorService.submit(new RenderPageTask());
}
}
运行时间较长的任务
如果线程池中线程的数量远小于在稳定状态下执行时间较长的任务的数量,那么到最后可能所有的线程都会运行这些执行时间过长的任务,从而影响整体的响应性。
如果线程池总是充满了阻塞任务,也有可能线程池规模较小
设置线程池大小
对于计算密集型的任务,在拥有 math N_{cpu}
个处理器的系统上,当线程池的大小为 math N_{cpu} + 1
时,通常能实现最优的利用率。即使当计算密集型的线程偶尔由于页缺失故障或其它原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
对于包含 IO 操作或其它阻塞操作的任务,由于线程并不会一直执行,因此线程池的规模应该更大。
获得 CPU 数目
int N_CPUS = Runtime.getRuntime().availableProcessors();
配置 ThreadPoolExecutor
ThreadPollExecutor 通用构造函数
public ThreadPollExecutor(int corePoolSize,
int maximumPoolSize,
long keepAvlieTime,
TimeUnit unit,
BlockingQueue<Runable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandle handle) { ... }