如何配置和调优线程池?
Executor 将任务的提交与任务的执行策略解耦开来。
不同的任务有不同的执行策略,以此考虑线程池的大小或配置限制:
- 独立的任务
- 依赖性的任务
- 在线程池中,如果一个任务等待其他任务的结果,可能出现线程饥饿死锁。
- 使用线程封闭机制的任务
- 单线程的Executor
- 运行时间较长的任务
- 任务阻塞时间过长。可以通过限制任务等待资源的时间。如限时版的Thread.join, BlockingQueue.put等
- 使用Threadlocal的任务
- 同类型的相互独立的任务
设置线程池大小
线程池过大,大量的线程在相当较少的处理器和内存资源上发生竞争,导致更高的内存使用率,可能耗尽资源;
线程池过小,可能导致许多空闲的处理器无法执行工作,从而降低吞吐量。
线程池大小受资源和任务特性影响, 资源如CPU,内存,文件句柄,套接字句柄,数据库连接池等。任务是计算密集型或IO密集型。
公式: N(threads) = N(CPU) * U(CPU) * (1 + W/C)
理想的线程池大小=CPU数量 * CPU的使用率 *(1+任务的等待时间/任务的计算时间)。
可以通过Runtime获取CPU的数目:
Runtime.getRuntime().availableProcessors();
配置ThreadPoolExecutor
可以通过ThreadPoolExecutor构造函数配置线程池:
public ThreadPoolExecutor (
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workqueue,
ThreadFactory threadFactory,
RejectedExecutorHandler handler)
参数详解:
corePoolSize: 基本大小,只有在工作队列满了的情况下创建超出这个数量的线程。
maximumPoolSize:最大大小,同时活动的线程数量上限。
keepAliveTime:存活时间,如果某个线程的空闲时间超过了存活时间,线程将被标记为可回收,当线程池的大小超过基本大小,这个线程将被终止。
workqueue: 保存等待执行的任务
任务的排队方法有3中:无界队列,有界队列和同步移交。
队列的选择和其他参数相关。
newFixedThreadPool & newSingleThreadPool 默认使用无界队列 LinkedBlockingQueue;
newCachedThreadPool使用SynchronousQueue;
如果非常大或无界的线程池,可以同过SynchronousQueue来避免排队。
如果任务相当独立,为线程池或队列设置界限才合理。
如果任务之间依赖,应使用无界线程池如newCachedThreadPool。
使用有界队列的时候有助于避免资源耗尽的情况,队列的大小和线程池的大小要一起调节。
handler:饱和策略
使用有界队列的时候,队列满时,任务如何处理?
四种饱和策略:
AbortPolicy(抛出异常), CallerRunsPolicy(任务退回到调用者), DiscardPolicy(抛弃该任务), DiscardOldestPolicy(抛弃最旧的任务)。
threadFactory:线程工厂
每当线程池创建线程时,都通过ThreadFactory的工厂方法newThread创建。可以定制ThreadFactory。
扩展ThreadPoolExecutor
ThreadPoolExecutor提供了在子类化中可以override的方法
beforeExecutor, afterExecutor, terminated, 这些方法可以用来添加日志,计时,监视或统计信息等功能。
示例:给线程池添加统计信息
public class TimingThreadPool extends ThreadPoolExecutor {
public TimingThreadPool() {
super(1, 1, 0L, TimeUnit.SECONDS, null);
}
private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private final Logger log = Logger.getLogger("TimingThreadPool");
private final AtomicLong numTasks = new AtomicLong();
private final AtomicLong totalTime = new AtomicLong();
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
log.fine(String.format("Thread %s: start %s", t, r));
startTime.set(System.nanoTime());
}
protected void afterExecute(Runnable r, Throwable t) {
try {
long endTime = System.nanoTime();
long taskTime = endTime - startTime.get();
numTasks.incrementAndGet();
totalTime.addAndGet(taskTime);
log.fine(String.format("Thread %s: end %s, time=%dns",
t, r, taskTime));
} finally {
super.afterExecute(r, t);
}
}
protected void terminated() {
try {
log.info(String.format("Terminated: avg time=%dns",
totalTime.get() / numTasks.get()));
} finally {
super.terminated();
}
}
}