今天来介绍一下面试几乎是必问的内容:线程池,那么在Java,JUC并发工具包中提供了Executors的工具类来操作线程池,而它实际就是使用的ThreadPoolExecutor,那下面就来从源码角度分析一下ThreadPoolExecutor。
属性
ctlctl是AtomicInteger类型的数值,它用来表示线程池的控制状态,ctl的值存储了两个不同的状态值:工作线程数和线程状态。ctl的前三位用来表示线程状态,后29位用来表示工作线程数线程运行状态RUNNING = -1 << COUNT_BITS(11100000 ... ... 00000000)SHUTDOWN = 0 << COUNT_BITS(00000000 ... ... 00000000)STOP = 1 << COUNT_BITS(00100000 ... ... 00000000)TIDYING = 2 << COUNT_BITS(01000000 ... ... 00000000)TERMINATED = 3 << COUNT_BITS(01100000 ... ... 00000000)其他属性值COUNT_BITS = Integer.SIZE - 3(29)CAPACITY = (1 << COUNT_BITS) - 1(2^29-1),用二进制表示:0001111 11111111 11111111 11111111工作线程数目、线程状态以及ctl值通过下面的这三个方法来计算出相应的值
构造函数
ThreadPoolExecutor的参数有哪些也是面试中经常问的题目,下面我们就拿参数最多的构造函数来介绍一下。
corePoolSize:线程池核心池数目,即使线程是空闲的,也不会被回收(allowCoreThreadTimeOut默认为false,如果设置为true,则核心池空闲时,会被回收)
maximumPoolSize:线程池中允许的最大线程数目
keepAliveTime:大于核心池的线程空闲时,能在线程池中存在的最大时间
unit:keepAliveTime的时间单位
workQueue:当大于核心池数目的任务提交时,会加入到队列中排队
threadFactory:创建线程的工厂
handler:当线程池满了后,线程池的拒绝策略
拒绝策略
jdk自带实现了4种拒绝策略。
AbortPolicy直接抛出异常RejectedExecutionExceptionCallerRunsPolicy由调用者来执行本次任务DiscardPolicy直接丢弃任务,什么都不处理DiscardOldestPolicy丢弃最旧的任务,再去execute本次任务execute方法
首先来看execute方法执行的三个步骤,然后再去看一下它的源码,详细分析线程池的实现原理。
如果线程池中运行的线程数目小于corePoolSize,尝试启动新的线程来执行任务如果任务能成功加入到队列中,仍然需要二次检查看是否需要创建一个线程(因为存在的一个线程可能在上次检查之后已经停止了)或者线程池已经shutdown了。因此再次检查这个状态,并且如果线程池停止了就需要把刚才入队的任务出队,或者启动一个新的线程如果任务加入队列失败,那么就去尝试添加一个新线程,如果失败,就去执行拒绝策略。
从execute方法源码也可以看出执行的三个步骤,下面来看一下addWorker方法,这个方法主要是创建线程,执行任务。
addWorker中会加入新线程,并启动线程,大家都知道线程启动后回去调用Runnable的run方法,这里也一样,会去调用Worker的run方法(Worker的run方法调用了runWorker方法,所以我们可以直接看runWorker方法的逻辑。省略了一些代码,只看主要的逻辑)
再来看一下从队列中取任务的getTask的逻辑(省略了一些代码,只看主要的逻辑)
总结
ThreadPoolExecutor的主要逻辑基本上都介绍了,最后整理了ThreadPoolExecutor的execute方法和processWorkerExit的流程图,大家可以参考。
更多的高并发知识,可以看视频去学,高并发已成为面试的敲门砖,如果有这方面的经验的话,offer基本上是手到擒来。