线程池的工作原理与源码解读
随着cpu核数越来越多,不可避免的利用多线程技术以充分利用其计算能力,所以多线程技术是服务端开发人员必须掌握的技术。
随着线程的创建和销毁,都涉及到系统的调用,比较消耗系统资源,所以就引入了线程池技术,避免频繁的创建和销毁线程。
在java中有一个Executors工具类,可以为我们创建一个线程池,其本质就是new一个ThreadPoolExecutor对象。线程池几乎也是面试题必考的问题。本文结合源码,说说ThreadPoolExecutor的工作原理。
线程池创建
先看一下ThreadPoolExecutor参数最全的构造方法:
public class ThreadPoolExecutor {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExceptionHandler handler){
}
}
- corePoolSize: 线程池中的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
- maximumPoolSize: 最大线程数,不管你提交多少任务,线程池里最多工作线程就是maximumPoolSize。
- keepAliveTime: 线程的存活时间。当线程池里的线程数大于corePoolSize时,如果登陆keepAliveTime时长还没有任务执行,则线程退出。
- unit: 这个是用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
- workQueue: 一个阻塞队列,提交的任务将会被放入到这个队列里。
- threadFactory:线程工厂,用来创建线程,主要是为了给线程命名,默认工厂的线程名:pool-1-thread-3。
- handler:拒绝策略,当线程池里线程被耗尽了,且队列也满了的时候会调用。
上面就是创建线程池用到的参数的详细解释。
线程池执行流程
这里用一个图来说明线程池的执行过程。
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列中,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接受任务。
这里以"1.8.0_171"的源代码为例,看一下具体实现。
- 先看一下线程池的execute方法
①. 判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务
②. 如果不小于corePoolSize,则将任务添加到workQueue队列中。
③. 如果放入workeQueu失败,则创建线程执行任务,如果创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。
- 再看下addWorker的方法实现
这块代码是在创建非核心线程时,即core为false。判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false,即上边说到③中的创建线程失败的情况。
addWorker方法的下半部分:
①. 创建Worker对象,用时也会实例化一个Thread对象。<br />
②. 启动这个线程
- 再到Woker里面看看其实现
可以看到在创建Worker时会调用threadFactory来创建一个线程,上面②中启动一个线程就会触发worker的run方法被线程调用。Worker实现了Runable接口。
- 接下来看看runWorker方法的逻辑
线程调用runWorker,会while循环调用getTask方法从workerQueue队列里读取任务,然后执行任务。只要getTask方法不返回null,此线程就不会退出。
- 最后在看看getTask方法实现
①. 先不管allowCoreThreadTimeOut,这个变量的默认值是false,wc > corePoolSize则是判断当前线程数是否大于corePoolSize。<br />
②. 如果当前线程数大于corePoolSize,这回调用wokerQueue的poll方法获取任务,超时时间就是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,上边提到的while循环就会退出,线程也就执行完了,如果当前线程数小于corePoolSize,则会调用wokerQueue的take方法阻塞在当前。