系列文章:
- Java多线程复习与巩固(一)–线程基本使用
- Java多线程复习与巩固(二)–线程相关工具类的使用
- Java多线程复习与巩固(三)–线程同步
- Java多线程复习与巩固(四)–synchronized的实现
- Java多线程复习与巩固(五)–生产者消费者问题(第一部分)
- Java多线程复习与巩固(六)–线程池ThreadPoolExecutor详解
- Java多线程复习与巩固(七)–任务调度线程池ScheduledThreadPoolExecutor
- Java多线程复习与巩固(八)–原子性操作与原子变量
- Java多线程复习与巩固(九)–volatile关键字与CAS操作
- ThreadPoolExecutor最佳实践–如何选择线程数
- ThreadPoolExecutor最佳实践–如何选择队列
去年的一篇《ThreadPoolExecutor详解》大致讲了ThreadPoolExecutor内部的代码实现。
总结一下,主要有以下四点:
- 当有任务提交的时候,会创建核心线程去执行任务(即使有核心线程空闲仍会创建);
- 当核心线程数达到corePoolSize时,后续提交的都会进BlockingQueue中排队;
- 当BlockingQueue满了(offer失败),就会创建临时线程(临时线程空闲超过一定时间后,会被销毁);
- 当线程总数达到maximumPoolSize时,后续提交的任务都会被RejectedExecutionHandler拒绝。
prestartAllCoreThreads方法可以直接创建所有核心线程并启动。
BlockingQueue使用无限容量的阻塞队列(如LinkedBlockingQueue)时,不会创建临时线程(因为队列不会满),所以线程数保持corePoolSize。
BlockingQueue使用没有容量的同步队列(如SynchronousQueue)时,任务不会入队,而是直接创建临时线程去执行任务。
虽然线程池的模型被剖析的非常清晰,但是如何最高性能地使用线程池一直是一个令人纠结的问题,其中最主要的问题就是如何决定线程池的大小。
这篇文章会以量化测试的方式分析:何种情况线程池应该使用多少线程数。
1. 计算密集型任务与IO密集型任务
大多数刚接触线程池的人会认为有一个准确的值作为线程数能让线程池适用在程序的各个地方。然而大多数情况下并没有放之四海而皆准的值,很多时候我们要根据任务类型来决定线程池大小以达到最佳性能。
计算密集型任务以CPU计算为主,这个过程中会涉及到一些内存数据的存取(速度明显快于IO),执行任务时CPU处于忙碌状态。
IO密集型任务以IO为主,比如读写磁盘文件、读写数据库、网络请求等阻塞操作,执行IO操作时,CPU处于等待状态,等待过程中操作系统会把CPU时间片分给其他线程。
2. 计算密集型任务
下面写一个计算密集型任务的例子:
public class ComputeThreadPoolTest {
final static ThreadPoolExecutor computeExecutor;
final static List<Callable<Long>> computeTasks;
final static int task_count = 5000;
static {
computeExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
// 创建5000个计算任务
computeTasks = new ArrayList<>(task_count);
for (int i = 0; i < task_count; i++) {
computeTasks.add(new ComputeTask());
}
}
static class ComputeTask implements Callable<Long> {
// 计算一至五十万数的总和(纯计算任务)
@Override
public Long call() {
long sum = 0;
for (long i = 0; i < 50_0000; i++) {
sum += i;
}
return sum;
}
}
public static void main(String[] args) throws InterruptedException {
// 我电脑是四核处理器
int processorsCount = Runtime.getRuntime().availableProcessors();
// 逐一增加线程池的线程数
for (int i = 1; i <= processorsCount * 5; i++) {
computeExecutor.setCorePoolSize(i);
computeExecutor.setMaximumPoolSize(i);
computeExecutor.prestartAllCoreThreads