什么是线程池,解决什么问题
线程池是一种池化技术,线程池解决了两个不同的问题:它们通常在执行大量异步任务时提供更好的性能,这是因为减少了每个任务的调用开销;它们提供了一种方法来限制和管理执行任务集合时消耗的资源,包括线程。每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。
其主要的作用线程的复用,减少了调用开销,管理消耗的资源。
使用线程池
public class PoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
//执行Runnable
pool.execute(() -> System.out.println(Thread.currentThread().getName()));
//执行Callable
Future<Integer> future = pool.submit(() -> {
System.out.println(Thread.currentThread().getName());
return 1024;
});
System.out.println(future.get());
}
}
使用线程池执行任务时,只要将我们自己实现的线程任务通过**execute()或submit()**方法执行即可。submit()最终调用的也是execute()方法。
ThreadPoolExecutor源码解读
1.为何不推荐使用Executors去创建jdk自带的线程池
阿里巴巴《Java开发手册》为何不推荐使用自带的线程,他也给出了理由,但我们需要了解线程池中各参数的具体意义。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
说明:
参数 | 含义 |
---|---|
corePoolSize | 线程池核心线程数 |
maximumPoolSize | 线程池最大线程数,达到最大值后线程池不会再增加线程 |
keepAliveTime | 线程池中超过核心线程的空闲线程最大存活时间 |
unit | 上个参数的时间单位 |
workQueue | 阻塞队列 |
threadFactory | 线程工厂 |
handler | 线程池满时的拒绝策略 |
打开Excutors源码我们发现,
创建FixedThreadPool和SingleThreadExecutor时,使用的是LinkedBlockingQueue,他的容量是int最大值,可能会堆积大量请求,导致OOM。
public LinkedBlockingQueue(){
this(Integer.MAX_VALUE);
}
创建CachedThreadPool时,线程池最大线程数为int最大值,虽然定义了存活时间,但同样可能创建大量线程,导致OOM。
2.ThreadPoolExecutor类中的属性与方法
了解线程池如何运行之前,我们要了解ThreadPoolExecutor类中一些重要属性与方法
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;// 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;// 二进制 00011111 11111111 11111111 11111111 ,表示线程数容量
说明:
属性 | 含义 |
---|---|
ctl | 一个原子整数封装,前3位记录线程池的状态,后29位记录线程数(这个比较重要,经常用到) |
COUNT_BITS | 有效线程占用的位数,定值29 |
CAPACITY | 有效线程的容量 |
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
说明:
属性 | 含义 | 二进制表示 |
---|---|---|
RUNNING | 接受新任务并处理排队的任务 | 11100000 00000000 00000000 00000000 |
SHUTDOWN | 不接受新任务,但处理排队的任务 | 00000000 00000000 00000000 00000000 |
STOP | 不接受新任务,不处理排队的任务,并中断正在进行的任务 | 00100000 00000000 00000000 00000000 |
TIDYING | 所有任务都已终止,workerCount为零,线程转换为TIDYING状态,将运行terminated()钩子方法 | 01000000 00000000 00000000 00000000 |
TERMINATED | terminated()方法执行完成,线程池终止了 | 01100000 00000000 00000000 00000000 |
我们可以发现,前三位不一样,对应不同状态,且5种状态对应的数值是递增的。他们的关系如下:
private final HashSet<Worker> workers = new HashSet<Worker>();
说明:
属性 | 含义 |
---|---|
workers | 包含池中所有工作线程的集。仅在获取mainLock时访问 |
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
说明:
方法 | 作用 |
---|---|
runStateOf(int c) | 通过ctl获取运行状态 |
int workerCountOf(int c) | 通过ctl获取工作线程数 |
ctlOf(int rs, int wc) | 通过运行状态与工作线程数计算出ctl |
3.拒绝策略
jdk默认的实现拒绝策略有四种:
3.1 AbortPolicy
3.2 CallerRunsPolicy
3.3 DiscardOldestPolicy
3.4 DiscardPolicy
3.任务执行流程及源码分析
execute()执行流程如下,submit()最终调用的也是execute():
execute()源码:
public void execute(Runnable command) {
// 程序健壮性判断
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 尝试核心线程启动任务
if (workerCountOf(c) < corePoolSize) {
// 这个方法代码较多,后面会介绍,大致就是任务成功加入工作线程,并成功启动会返回true
if (addWorker(command, true))
return;
// addWorker()不是整个方法被锁住的,添加任务时,其他的线程可能捷足先登了,所以重新获取ctl,进行判断
c = ctl.get();
}
// 核心线程已满,尝试加入队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
// 这里提前准备空任务线程,为处理队列中的任务做准备
addWorker(null, false);
}
// 加入队列失败,尝试使用非核心线程启动,启动失败,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
小结:通过execute()方法可以发现,流程与上面的执行流程是一致。所以后来的任务,可能会早于之前的任务执行。因为可能有任务在排队,而无法立即启动,而后来的却用非核心线程直接启动了。
addWorker()源码:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// rs >= SHUTDOWN 如果线程池状态为RUNNING,则直接跳出判断执行后面的循环,尝试加入任务
// ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()) 注意外层取反,所以里面只要有一个判断为false,则程序直接return false
// rs == SHUTDOWN 如果状态不是SHUTDOWN,可能为STOP,TIDYING,TERMINATED,这些状态没有加入任务的必要了, 程序直接return false
// firstTask == null SHUTDOWN状态,由前面得知,不接受新任务,所以任务不为空,程序直接return false
// ! workQueue.isEmpty() 结合外层取反一起看,得到workQueue.isEmpty(),如果阻塞队列为空,则判断为true,程序直接return false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 超出容量无法继续创建
// 超出核心线程数量,由execute()我们得知,会尝试用非核心线程去启动任务
// 超出最大线程数量,则无法创建,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 以cas方式,将ctl值加1,成功增加,则代表工作线程总数加1
// 成功增加会跳出外层循环执行后面的程序
if (compareAndIncrementWorkerCount(c))
break retry;
// 到达这里代表代表加1失败,则尝试重新增加。状态一致执行内循环,状态不一致从外循环重新开始
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
// 到达这里代表compareAndIncrementWorkerCount(c)成功,则说明有位置供我们新的任务去启动
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 封装一个Worker,构造方法代码很少,但有个地方很重要,后面会讲
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 这里加锁,防止在我们后续操作中,线程池状态等发生改变
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN 状态为RUNNING时为true
// rs == SHUTDOWN && firstTask == null 状态为RUNNING为SHUTDOWN,需要创建空的任务线程,去处理阻塞队列任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 任务如果已经启动,则无法再次启动,直接抛异常
if (t.isAlive())
throw new IllegalThreadStateException();
// 加入工作线程
workers.add(w);
int s = workers.size();
// 记录工作线程最大值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 这里后续会启动Worker的run()方法
t.start();
workerStarted = true;
}
}
} finally {
// 启动失败,执行addWorkerFailed()
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
小结:addWorker()针对RUNNING和SHUTDOWN状态。如果为RUNNING,则创建线程去处理任务,如果为SHUTDOWN,则有可能创建空任务线程处理阻塞队列任务
内部类Worker源码:
Worker(Runnable firstTask) {
// AQS相关,先不讨论
setState(-1);
this.firstTask = firstTask;
// 这里我们发现,new Thread时,传入的是Worker自己,那么后续启动线程时,则会调用Worker的run()方法
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 任务不为空时,则执行现有任务
// 任务为空时,则通过getTask()获取任务,后面介绍
// getTask() 体现线程的复用
while (task != null || (task = getTask()) != null) {
// 进入循环代表有任务获取到
w.lock();
// 如果状态是STOP及以上的话,则主动中断当前线程
// 如果状态是STOP以下的话,确保当前线程不是中断状态并二次检测线程池状态
// 总结就是如果线程池STOP或者更高状态,我们就要放弃任务处理了
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
// 主动中断当前线程
wt.interrupt();
try {
// 前置处理,未实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 到这里我们的任务就真正的执行了
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 前置处理,未实现
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 主要是记录完成的任务总数以及从工作队列中移除此次任务
processWorkerExit(w, completedAbruptly);
}
}
小结:这里主要为后续调用Worker的run()方法做准备,runWorker()里面才会真正启动任务。通过getTask()实现线程复用。
getTask()源码:
private Runnable getTask() {
// 上次poll()是否超时
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 线程池状态>=SHUTDOWN且(线程池状态>=STOP或 工作队列为空),则返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// allowCoreThreadTimeOut 核心线程是否允许被淘汰,默认为false,我们按false分析
// wc > corePoolSize 工作线程数大于核心线程数时为true,否则为false
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 工作线程数大于最大线程数时,减少ctl值,但是不返回对象
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 工作线程数大于核心线程数时,执行workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
// 工作线程数小于等于核心线程数时,执行workQueue.take()
// 这两个方法都会取出队列的任务,不同的是poll会有一段时间的阻塞(前提定义了存活时间)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
小结:getTask()会有3个retun,如果不是状态或者工作线程数量的问题,他会一直循环读取队列的任务,从而达到复用。
processWorkerExit()源码:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果突然完成,则未调整workerCount,我们需要在这里进行调整
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 如果设置了存活时间keepAliveTime,runWorker()的while()里就可能获取null,从而跳出循环,达到清楚空闲线程的功能
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
小结:这段代码是处理线程结束是的一些操作,可以达到清楚空闲线程的功能,但有些属性私有的,比如completedTaskCount ,外部无法查看,开发通过jvm看吗?
END