1.线程池的核心属性
//ctl是int类型,而int是32个bit位组成的数值
//高3位记录:线程池的状态 低29位记录:工作线程个数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//这个29就是为了方便对int类型数值进行分割
private static final int COUNT_BITS = Integer.SIZE - 3;
//工作线程最大个数
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
//高3位记录的线程池状态
//RUNNINT:属于正常状态,可以正常接收任务,并且处理任务
private static final int RUNNING = -1 << COUNT_BITS;
//SHUTDOWN:处理关闭状态,不接收新的任务,但是会处理完线程池中之前接收到的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
//STOP:处理停止状态,不接收新的任务,中断正在执行的任务,之前接收到的任务也不处理
private static final int STOP = 1 << COUNT_BITS;
//TIDYING:过渡状态,是从SHUTDOWN或者STOP转换过来的,工作线程挂了,任务也处理完了
private static final int TIDYING = 2 << COUNT_BITS;
//TERMINATED:终止状态,是从TIDYING执行了terminated方法,转换到TERMINATED
private static final int TERMINATED = 3 << COUNT_BITS;
2.线程池的7个参数
//核心线程数
public ThreadPoolExecutor(int corePoolSize,
//最大线程数,在核心线程之外的非核心线程数 = maximumPoolSize-corePoolSize
int maximumPoolSize,
//最大空闲时间(一般针对非核心线程)
long keepAliveTime,
//时间单位(一般针对非核心线程)
TimeUnit unit,
//工作队列,核心线程个数满足corePoolSize,再来任务就扔到工作队列
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
3.线程池的4种拒绝策略
CallerRunsPolicy:线程池让调用者去执行。
AbortPolicy:如果线程池拒绝了任务,直接报错。
DiscardPolicy:如果线程池拒绝了任务,直接丢弃。
DiscardOldestPolicy:如果线程池拒绝了任务,直接将线程池中最旧的,未运行的任务丢弃,将新任务入队。
4.execute方法源码
public void execute(Runnable command) {
//非空判断
if (command == null)
throw new NullPointerException();
//获取ctl属性
int c = ctl.get();
//如果工作线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//通过addWorker方法创建核心线程
if (addWorker(command, true))
//创建成功,任务交给线程执行,方法结束
return;
//到这,说明失败了,走下一流程
c = ctl.get();
}
//判断线程池状态是不是RUNNING,通过offer方法往工作队列添加任务
//offer方法:添加成功返回true,添加失败返回false,不阻塞
if (isRunning(c) && workQueue.offer(command)) {
//任务已经放到工作队列了,要重新检查一次
int recheck = ctl.get();
//判断线程池是不是RUNNING
//如果不是RUNNING,将刚刚放进来的任务从队列移除
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//如果状态是RUNNING,同时工作线程数为0
else if (workerCountOf(recheck) == 0)
//如果没有工作线程,添加一个非核心线程去解决掉工作队列没有处理的任务
addWorker(null, false);
}
//addworker创建非核心线程,如果成功返回true
//如果添加非核心线程失败,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
//判断是否可以正常添加工作线程
retry:
for (int c = ctl.get();;) {
// 如果线程池不是RUNNING状态(到这就不能添加工作线程了)
if (runStateAtLeast(c, SHUTDOWN)
//并且线程池状态不是SHUTDOWN(是STOP或TIDYING或TERMINATED),或者任务不是空,或者工作队列为空,则返回false
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
//如果工作现场个数,大于等于线程池允许的工作线程数的最大值,则不能添加
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
//基于CAS,对ctl进行+1操作
if (compareAndIncrementWorkerCount(c))
//如果CAS成功,直接跳出外层循环,执行添加工作线程,并且启动工作线程
break retry;
//重新获取ctl
c = ctl.get(); // Re-read ctl
//如果不是RUNNING状态,重新判断线程池的状态,走外部循环
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建工作线程
w = new Worker(firstTask);
//获取到工作线程中的Thread
final Thread t = w.thread;
//这个if几乎不会发生,除非是你写的ThreadFactory有问题,或者业务没有通过
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int c = ctl.get();
//判断是否RUNNING状态
if (isRunning(c) ||
//runStateLessThan(c, STOP) = SHUTDOWN
//状态是SHUTDOWN,并且任务是空
(runStateLessThan(c, STOP) && firstTask == null)) {
//除非你写的ThreadFactory有问题,或者业务没通过,这里就抛异常了
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
//添加worker工作线程到workers
workers.add(w);
//工作线程添加成功标识true
workerAdded = true;
//记录工作线程的历史最大值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
//添加工作线程成功就启动线程
if (workerAdded) {
t.start();
//启动成功标识true
workerStarted = true;
}
}
} finally {
//工作线程启动失败
if (! workerStarted)
//将worker从hashSet移除,并且对ctl-1(即工作线程数-1)
addWorkerFailed(w);
}
return workerStarted;
}
5.run方法源码
public void run() {
runWorker(this);
}
runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
//将worker中的任务置为空
w.firstTask = null;
//置互斥锁状态为0,此时可以被中断。
w.unlock(); // allow interrupts
//用于标记完成任务时是否有异常。
boolean completedAbruptly = true;
try {
// 循环:初始任务(首次)或者从阻塞阻塞队列里拿一个(后续)。
while (task != null || (task = getTask()) != null) {
//获取互斥锁。
//在持有互斥锁时,调用线程池shutdown方法不会中断该线程。
//但是shutdownNow方法无视互斥锁,会中断所有线程。
w.lock();
//线程池处于STOP、TIDYING、TERMINATED状态,处于这些状态的线程池是无法执行的
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//中断线程
wt.interrupt();
try {
//前置增强(钩子函数)
beforeExecute(wt, task);
try {
task.run();
//后置增强(钩子函数)
afterExecute(task, null);
} catch (Throwable ex) {
//后置增强(钩子函数)
afterExecute(task, ex);
throw ex;
}
} finally {
//任务置空
task = null;
//记录当前worker完成了一个任务
w.completedTasks++;
//释放互斥锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
//处理工作线程退出。
//上面主循环中的前置处理、任务调用、后置处理都是可能会抛出异常的。
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
/**
* 工作线程从任务队列中拿取任务的核心方法。
* 根据配置决定采用阻塞或是时限获取。
* 在以下几种情况会返回null从而接下来线程会退出(runWorker方法循环结束):
* 1. 当前工作线程数超过了maximumPoolSize(由于maximumPoolSize可以动态调整,这是可能的)。
* 2. 线程池状态为STOP (因为STOP状态不处理任务队列中的任务了)。
* 3. 线程池状态为SHUTDOWN,任务队列为空 (因为SHUTDOWN状态仍然需要处理等待中任务)。
* 4. 根据线程池参数状态以及线程是否空闲超过keepAliveTime决定是否退出当前工作线程。
*/
private Runnable getTask() {
// 上次从任务队列poll任务是否超时。
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
//如果线程池状态已经不是RUNNING状态了,则设置ctl的工作线程数-1
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
/*
* allowCoreThreadTimeOut是用于设置核心线程是否受keepAliveTime影响。
* 在allowCoreThreadTimeOut为true或者工作线程数>corePoolSize情况下,
* 当前工作线程受keepAliveTime影响。
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 1. 工作线程数>maximumPoolSize,当前工作线程需要退出。
* 2. timed && timedOut == true说明当前线程受keepAliveTime影响且上次获取任务超时。
* 这种情况下只要当前线程不是最后一个工作线程或者任务队列为空,则可以退出。
* 换句话说就是,如果队列不为空,则当前线程不能是最后一个工作线程,
* 否则退出了就没线程处理任务了。
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 设置ctl的workCount减1, CAS失败则需要重试(因为上面if中的条件可能不满足了)。
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根据timed变量的值决定是时限获取或是阻塞获取任务队列中的任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// workQueue.take是不会返回null的,因此说明poll超时了。
timedOut = true;
} catch (InterruptedException retry) {
// 在阻塞队列上等待时如果被中断,则清除超时标识重试一次循环。
timedOut = false;
}
}
}
6.线程池面试题
ThreadPoolExecutor 有哪些常用的方法?
ThreadPoolExecutor有如下常用方法:
-
submit()/execute():执行线程池
-
shutdown()/shutdownNow():终止线程池
-
isShutdown():判断线程是否终止
-
getActiveCount():正在运行的线程数
-
getCorePoolSize():获取核心线程数
-
getMaximumPoolSize():获取最大线程数
-
getQueue():获取线程池中的任务队列
-
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程这些方法可以用来终止线程池、线程池监控等。
说说submit和 execute两个方法有什么区别?
提交任务类型 | 是否抛异常 | 有无返回值 | |
---|---|---|---|
execute | Runnable类型 | 是 | 无 |
submit | Runnable类型、Callable类型 | 否,但是Future的get方法可以将异常重新抛出 | Runnable类型的任务返回为null,Callable类型的任务返回值为Future |
shutdownNow() 和 shutdown() 两个方法有什么区别?
shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
了解过线程池的工作原理吗?
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
线程池中核心线程数量大小怎么设置?
「CPU密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为CPU核心数+1。因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
「IO密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为2*CPU核心数。IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
另外:线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;
以上只是理论值,实际项目中建议在本地或者测试环境进行多次调优,找到相对理想的值大小。
线程池为什么需要使用阻塞队列?
一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞来获取任务的线程(即线程池中没有任务时,阻塞核心线程),使得线程进入wait状态,释放cpu
阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占有cpu资源。
为什么是先添加队列而不是先创建最大线程?
在创建新线程的时候,是要获取全局锁的,这个时候其他的线程就需要阻塞,影响了整体的性能。
了解线程池的状态吗?
RUNNING
状态说明:线程池处于RUNNING状态时,能够接收新任务以及对已添加的任务进行处理。
状态切换:线程池的初始状态为RUNNING。换句话说线程池一旦被创建,就处于RUNNING状态,且线程池中的任务数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
SHUTDOWN
状态说明:线程池处于SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN
STOP
状态说明:线程池处于STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING)或者(SHUTDOWN)->STOP
TIDYING
状态说明:当所有的任务已终止,ctl记录的任务数为0,线程池的状态会变为TIDYING状态;当线程池的状态变为TIDYING状态时,会调用钩子函数terminated(),该方法在ThreadPoolExecutor中是空的,若用户想在线程池变为TIDYING时进行相应的处理,就需要重载terminated()函数实现
状态切换:当线程池状态为SHUTDOWN时,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUTDOWN->TIDYING
当线程池为STOP时,线程池中执行的任务为空时,就会又STOP->TIDYING
TERMINATED
状态说明:线程池彻底终止,就会变成TERMINATED状态
状态切换:线程池处于TIDYING状态时,调用terminated()就会由TIDYING->TERMINATED
知道线程池中线程复用原理吗?
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。