线程池的核心类 ThreadPoolExecutor
我常用的线程池的创建,都是使用基于ThreadPoolExecutor的类去实现的工具类Executors
首先我们来了解一下提供的构造方法
// 第一种构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 第二种构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 第三种构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//第四种构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor这个线程池的核心类中一共给我们提供了四种构造方法,我们从这四个构造方法中找出其核心参数,进行简单的解释。
1、corePoolSize:线程池的核心线程数量
2、maximumPoolSize线程池中允许的最大线数。
3、keepAliveTime :当线程数大于核心线程时,终止多余线程等待新任务的最长时间。
4、 workQueue :存放任务队列。
5、threadFactory:执行程序创建新线程时使用的工厂。
6、handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
7、unit- keepAliveTime 参数的时间单位。
线程池的实现原理:
1、用户提交一个任务到线程池
2、判断当前运行的worker数量是否超过corePoolSize,如果不超过corePoolSize,就创建一个工作线程执行该任务。
3、如果worker数量超过corePoolSize,,那么就将该任务加入到workQueue队列中去。
4、如果workQueue队列数量不超过maximumPoolSize,就创建一个工作线程执行该任务。
5、如果大于maximumPoolSize,就执行性RejectedExecutionHandler拒绝这个任务的提交。
注:线程池最开始是没有worker在运行的 ,maximumPoolSize查看源代码我们会发现,这个值最大是2^29-1=536870911我想至今为止还没有真实的程序开启超过这个最大值的线程数。
如何合理的配置线程池?
要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:
任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
任务的优先级:高,中和低。
任务的执行时间:长,中和短。
任务的依赖性:是否依赖其他系统资源,如数据库连接。
直白一点
任务性质不同的任务可以用不同规模的线程池分开处理。
CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。
IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。
混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
(N为CPU总核数)
- 如果是CPU密集型应用,则线程池大小设置为N+1
- 如果是IO密集型应用,则线程池大小设置为2N+1