线程是cpu的一种资源,在java应用中通过newThread可以很容易的创建一个线程,但如果不对线程的创建进行适当的控制,则应用在高并发的情况下很容易出现cpu资源耗尽导致服务宕机挂掉,jdk自1.5之后提供给了一个并发包,其中的线程池ThreadPoolExecutor可以帮助我们有效的控制线程数量以及处理高并发的任务,因为它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源耗尽和宕机,今天主要是通过源码分析jdk1.8下的线程池的工作原理

原创:引用请著名出处

一、概念

java是一门面向对象的语言,线程以及线程池在java中也是一种对象资源,在开始介绍线程池工作原理之前,先简单介绍线程池里面主要用到的几个参数的概念

1.核心线程:线程池运行以后接收到新的任务时创建核心线程,核心线程默认不会随任务执行完后销毁

2.非核心线程:当程序运行过程中,当前的工作线程数大于等于核心线程数且小于创建线程池时定义的最大线程数,以及阻塞队列无法再添加新的RunnableTask时,创建新的非核心线程去执行任务,任务执行完后销毁(若有设置超时时间,则非核心线程不会在任务执行完后立即销毁,而是在超时时间后仍未得到runnableTask后销毁)

3.workQueue(阻塞队列):线程池在初始化的时候需要定义一个阻塞队列对象,用于存放暂时处理不过来的RunnableTask

4.workers:一个HashSet集合,用来存储Worker(可理解为工作线程对象)

5.拒绝策略:当线程池无法处理新的RunnableTask时,执行拒绝策略,默认的是抛异常中断拒绝,也可以自定义策略

二、线程池的创建

要了解线程池的工作原理,我们可以首先了解一下它是如何创建的,它的构造函数,如下图

可以看到,主要就是初始化一些参数、如核心线程数和最大线程数大小、超时时间、阻塞队列、拒绝策略等

三、Worker

1.Worker对象

Worker是ThreadPoolExecutor的一个实现了Runnable接口的私有内部类,在执行线程池的execute方法创建线程时会创建到这个对象,如果创建的是核心线程的worker,则在任务执行完之后会重新添加到workers这个hashset集合里面(后面会讲到),这个类的成员变量主要有一个表示当前worker正在执行的线程thread,以及要运行的初始化任务firstTask(Runnable),另外,它的run方法委托给了外部的一个runWorker方法执行,具体源码如下

2、addWorke方法


方法入参说明:addWorker是ThreadPoolExecutor的一个私有方法,有2个参数,一个是Runnable类型(args1),另一个是boolean类型(args2)

  • args1,null表示将从workQueue拉取一个RunnableTask去创建Worker,否则拿当前的RunnableTask去创建Worker
  • args2,true表示创建的是核心线程的worker,false表示创建的是非核心线程的worker

代码执行说明:这里为了方便理解,我把这个方法分为2个部分,一部分是自旋部分(part1),另一部分是退出自旋退出后的逻辑处理部分(part2)

part1:自旋部分的代码有内外2层,外层刚进来时判断当前线程池状态是否是运行状态,正常运行则接着往下走进入内层自旋,内层自旋首先是处理线程数的控制判断,若当前工作线程数大于等于定义的目标线程数则直接返回失败,否则执行cas操作当前工作线程数+1,成功+1则break退出外层自旋,否则执行cas失败则重新从外层循环进来(可以看到part1主要做的事情就是添加工作线程数)

part2:在part1执行成功将当前工作线程数+1之后来到part2代码接着往下运行,首先是创建一个Worker,然后把该Worker添加到workers集合里面,添加成功则启动worker线程,worker线程运行时会委托外部的一个runWorker方法执行work,源码如下

以上就是addWorker方法主要做的事情,接着我们继续看看委托到外部执行的runWorker做了什么事情

3.runWorker方法

该方法首先获取RunnableTask,先从worker里面取初始化任务,若为空,则调用getTask()方法从阻塞队列worksQueue里面取

a.若获取任务成功,则进入一段自旋代码,调用beforeExecute,清理当前线程局部变量表或执行日志记录,然后执行Task的run方法,执行完成后继续自旋直到获取不到Task时退出自旋,然后执行processWorkerExit方法处理一些线程销毁的内容或者将核心线程worker重新加入到hashSet集合,具体源码如下

接着,我们继续来看processWorkerExit是怎么处理worker销毁工作的,首先说明一下,方法执行时使用到了一个线程池的实例布尔变量allowCoreThreadTimeOut,这个变量用来控制是否允许核心线程超时,默认是false,即核心线程不会销毁。

这段代码很短,比较简单,入参有2个,一个是Worker对象,另一个是boolean变量表示线程是否非正常结束(即runWorker方法跳出自旋时),如果线程是正常结束,该值为false

首先将worker从hashSet集合中移出,执行线程数-1等退出操作,然后判断当前工作线程数是否大于等于核心线程数,如果是则直接返回,当前worker结束,否则再次调用addWorker方法,将该worker重新加入到hashSet集合中,源码如下

四、ThreadPoolExecutor.execute

最后再来看线程池的execute方法,若前面关于Worker的几个方法都理解了,那接下来的这个方法理解起来就很容易了

1.如果当前工作线程数小于核心线程数,则调用核心线程方式的addWorker方法添加新的worker执行任务

–添加成功,则使用这个worker的线程去执行任务,execute方法结束

–添加失败,则把任务塞到workQueue(如果塞入队列失败尝试调用addWorker创建非核心线程执行,若执行也失败则执行拒绝策略),execute方法结束

2.如果当前工作线程大于或者等于核心线程数,则将任务塞到workQueue

–塞入成功,继续判断当前工作线程数是否等于0

—-是,则调用非核心线程方式的addWorker方法,从workQueue里面拉取一个任务出来执行,结束

—-否,execute方法结束

–塞入失败,调用非核心线程方式的addWorker方法,尝试执行当前的Runnable,若执行还是失败则执行拒绝策略

具体源码如下:

五、总结

到这里,线程池工作原理的源码分析就结束了,本文只是粗略的对一些主要的步骤进行了分析,帮助读者理解线程池从创建线程到执行任务到最后销毁大概是一个怎样的过程,有一些细节的东西感兴趣的同学可以自己去看看jdk的源码,其实也并不难理解,只是设计得非常巧妙!

发表评论

电子邮件地址不会被公开。