网上一堆 ThreadPoolExecutor 的解读,有些可能还相互矛盾,其实 ThreadPoolExecutor类的注释中就有大量的说明,本文基于jdk1.8.0中代码注释加上自己的一点理解与实践
一、为什么使用线程池
线程池主要解决2个方面的问题:
- 提升性能
在执行大量异步任务时,由于减少了任务执行的开销而明显提升性能。并且在执行一系列任务集合时,线程池提供了一种限制并管理资源(包括线程在内)的一种方法。
按我的理解,线程池能够重用之前的线程,甚至可以预创建线程,这就在实际任务执行时减少了创建线程的开销,进而提升性能。 - 线程池维护了一些基本的统计信息:例如已完成的任务数
二、线程池基本工作原理
我们先来看下创建线程池的基本参数
参数 | 说明 |
---|---|
int coolPoolSize | 核心线程数 |
int maxmumPoolSize | 最大线程数 |
long keepAliveTime | 存活时间 |
TimeUnit unit | keepAliveTime参数的单位 |
BlockingQueue workQueue | 线程池中的工作队列 |
threadFactory | 线程工厂,负责创建线程 |
RejectedExecutionHandler handler | 拒绝策略 |
1、 corePoolSize 和 maxmumPoolSize
线程池执行器会根据corePoolSize 和 maxmumPoolSize的值自动调整线程池的大小(getPoolSize: 获取线程池中当前线程数量)。
当一个新的任务提交(execute方法)过来时,如果线程池中的线程数小于corePoolSize时,创建一个新的线程来处理这个任务,哪怕其它工作线程是空闲的。如果线程池中的线程数大于等于corePoolSize并且小于maxmumPoolSize时,如果工作队列已经满了,创建一个新线程,如果工作队列没满,则新任务放入工作队列中。通过将maxmumPoolSize 设置为一个无边界的值(例如Integer.MAX_VALUE),线程池可以容纳任意数量的并发任务。通常情况下是通过构造函数来设置corePoolSize 和 maxmumPoolSize的,但是也可以通过setCorePoolSize() 和setMaxmumPoolSize()方法动态改变它的值
线程池工作原理示意图:
默认情况下,核心线程是只有在新的任务到来的时候才会创建并执行的,就是说线程池刚初始化的时候里面是没有任何线程的。但是可以通过调用prestartCoreThread 或者 prestartAllCoreThreads 方法来预先启动线程。
boolean preStartCoreThread() 启动一个空闲的核心线程,等待任务到来。如果所有的核心线程都已经启动,返回false
int prestartAllCoreThreads() 启动所有的核心线程,并置为空闲等待任务到来。返回启动的线程数,也就是说如果之前已经启动了所有核心线程,调用该方法是不做任何操作的
代码示例
public static void main(String[] args) throws Exception{
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 3L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10));
boolean b = threadPoolExecutor.prestartCoreThread(); // 启动一个核心线程,成功
System.out.println(b);
b = threadPoolExecutor.prestartCoreThread(); // 启动一个核心线程,成功
System.out.println(b);
b = threadPoolExecutor.prestartCoreThread();// 启动一个核心线程,成功
System.out.println(b);
b = threadPoolExecutor.prestartCoreThread();// 启动一个核心线程,核心线程数为3,前面已经全部启动,这次失败
System.out.println(b);
System.out.println(threadPoolExecutor.getPoolSize());
int i = threadPoolExecutor.prestartAllCoreThreads(); // 启动所有核心线程,因为之前已经启动了所有核心线程,返回0
System.out.println(i);
System.out.println(threadPoolExecutor.getPoolSize());
}
执行结果:
true
true
true
false
3
0
3
2、 keepAliveTime 和 unit
线程池里的线程数大于corePoolSize时,多余的线程在空闲超过keepAliveTime时间后将被终止。也可以用setKeepAliveTime方法来动态修改这个值。把这个值设置为Long.MAX_VALUE,TimeUnit.NANOSECONDS 可以有效防止在线程池关闭之前线程被终止。
上面强调是超过corePoolSize的多余线程才有用,也就是说默认情况下当线程池中线程数少于等于核心线程数时keepTimeAlive不会起作用。但是可以调用allowCoreThreadTimeOut(boolean)来将覆盖这种策略,前提是keepAliveTime>0。
我觉得需要注意的是:并没有核心线程这种东西,没有说核心线程数为3的线程池中最先启动的3条线程就是核心线程,后面启动的线程是非核心线程,只有核心线程数这个数量。以核心线程数为3,最大线程数为5,keepAliveTime=3秒为例的一个线程池,第一个启动的线程标记为t1,如果池中的线程数大于3条,并且t1的空闲时间超过3秒,那么t1将被终止,即使它是第一个启动的线程。不存在核心线程这种概念
我们做一个实验: 创建一个核心线程数为3,最大线程数为5,keepAliveTime=3秒,SynchronousQueue为工作队列的一个线程池。SynchronousQueue可以理解为一个转接器,有任务交给它,它立刻就转交给线程去执行
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 3L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
//threadPoolExecutor.allowCoreThreadTimeOut(true);
for(int i = 1; i<=4; i++) {
final int j = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(j+": "+Thread.currentThread().getName()+ " begin,this will last "+j+"s");
try {
Thread.sleep(j * 1000);
} catch (Exception e) {
}
System.out.println(j+": "+Thread.currentThread().getName()+ " end");
}
});
}
Thread.sleep(1100L); // 确保任务线程先执行完,主线程等待时间略加了一点
System.out.println("1 秒以后PoolSize: "+threadPoolExecutor.getPoolSize());
System.out.println("1 秒以后ActiveCount: "+threadPoolExecutor.getActiveCount());
Thread.sleep(1000L);
System.out.println("2 秒以后PoolSize: "+threadPoolExecutor.getPoolSize());
System.out.println("2 秒以后ActiveCount: "+threadPoolExecutor.getActiveCount());
Thread.sleep(1000L);
System.out.println("3 秒以后PoolSize: "+threadPoolExecutor.getPoolSize());
System.out.println("3 秒以后ActiveCount: "+threadPoolExecutor.getActiveCount());
Thread.sleep(1000L);
System.out.println("4 秒以后PoolSize: "+threadPoolExecutor.getPoolSize());
System.out.println("4 秒以后ActiveCount: "+threadPoolExecutor.getActiveCount());
Thread.sleep(1000L);
System.out.println("5 秒以后PoolSize: "+threadPoolExecutor.getPoolSize());
System.out.println("5 秒以后ActiveCount: "+threadPoolExecutor.getActiveCount());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("5: "+Thread.currentThread().getName()+ " begin,this will last 5s");
try {
Thread.sleep(5 * 1000);
} catch (Exception e) {
}
System.out.println("5: "+Thread.currentThread().getName()+ " end");
}
})