线程池原理
Posted panning
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池原理相关的知识,希望对你有一定的参考价值。
一、线程池的作用
线程池类似于数据库链接池、Redis链接池等池化技术。池化技术的优点如下:
1. 统一管理资源,线程是操作系统一个重要监控管理指标,过多的线程会导致占用内存、上下文切换频繁等问题,所以需要管理起来线程,而每处都用new Thread()方法来创建线程,那线程资源散落在应用程序各地,没法管理。
2. 不需要每次要用到线程时都再次创建一个新的线程,可以做到线程重用。线程池默认初始化时是没有创建线程的(也可以在创建线程池时自动创建好核心线程),线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。
二、Java中提供的创建线程池的API
先来解释一下每个参数的作用,稍后我们在分析源码的过程中再来详细了解参数的意义。
public ThreadPoolExecutor(int corePoolSize, // 核心线程数 int maximumPoolSize,// 最大线程数 long keepAliveTime,// 非核心线程数空闲时,回收时长 TimeUnit unit,// 回收时长单位 BlockingQueue<Runnable> workQueue,// 阻塞队列 RejectedExecutionHandler handler/** 拒绝策略*/) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
1. 线程数少于核心线程数(也就是设置的线程数)时,新建线程执行任务;
2. 线程数等于核心线程数后,将任务加入阻塞队列;
3. 由于队列容量非常大,所以可以一直添加;
4. 执行完任务的线程反复去队列中取任务执行;
用途:FixedThreadPool 用于负载比较大的服务器,为了资源的合理利用,需要限制当前线程数量
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, // 不会有非核心线程,所以回收时间间隔为0 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); }
2. CachedThreadPool:核心线程数为0,所有任务全部进入阻塞队列,最大线程数是Integer.MAX_VALUE,不会有拒绝策略。所有线程执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收。
它的执行流程如下:
1. 没有核心线程,直接向 SynchronousQueue 中提交任务
2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收
/* * 核心线程数为0(没有核心线程,直接向 SynchronousQueue 中提交任务), * 最大线程数是Integer.MAX_VALUE,不会有拒绝策略,导致大量线程的创建出现 CPU 使用过高或者 OOM 的问题 * 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收 */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); }
3. newSingleThreadExecutor():核心线程数=最大线程数=1,使用无界阻塞队列。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
三、线程池的实现原理分析:
源码分析:
ThreadPoolExecutor的execute()方法:
/**
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 线程池初始化时是没有创建线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,
* 而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
* 这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销
*
* 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
* 在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
* prestartCoreThread():初始化一个核心线程
* prestartAllCoreThreads():初始化所有核心线程
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.当前池中线程比核心数少,新建一个线程执行任务
if (workerCountOf(c) < corePoolSize) {
// 创建新的线程并执行任务,如果成功就返回
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.核心池已满但任务队列未满,将任务添加到队列中
if (isRunning(c) && workQueue.offer(command)) {
//重新获取ctl
int recheck = ctl.get();
//任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
//如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
if (!isRunning(recheck) && remove(command))
reject(command);
//如果之前的线程已被销毁完,新建一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
/*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
*/
} else if (!addWorker(command, false))
// 如果线程池是非RUNNING状态或者加入阻塞队列失败,则尝试创建新非核心线程(外包)直到maxPoolSize
// 创建非核心线程失败,则启动拒绝策略
reject(command);
}
其中ctl就是一个AutomicInteger的变量,用于存储线程数量(低29位)和线程池的状态(高3位)。
线程池状态如下:
/** * 即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务; * 111 0 0000 0000 0000 0000 0000 0000 0000 * -1 原码:0000 ... 0001 反码:1111 ... 1110 补码:1111 ... 1111 * 左移操作:后面补 0 * 111 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int RUNNING = -1 << COUNT_BITS; /** * 即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务; * 000 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int SHUTDOWN = 0 << COUNT_BITS; /** * 即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务; * 001 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int STOP = 1 << COUNT_BITS; /** * 即高3位为010,所有任务都已终止,workerCount为零,过渡到状态TIDYING的线程将运行terminated()钩子方法; * 010 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int TIDYING = 2 << COUNT_BITS; /** * 即高3位为011,terminated()方法执行完毕; * 011 0 0000 0000 0000 0000 0000 0000 0000 */ private static final int TERMINATED = 3 << COUNT_BITS;
/** * firstTask参数用于表示怎么获取线程处理的任务,true为传入的任务,false表示从阻塞队列获取任务 * core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize, * false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize */ private boolean addWorker(Runnable firstTask, boolean core) { // 内嵌循环,通过CAS worker + 1 retry: for (; ; ) { // 获取当前线程池状态与线程数 int c = ctl.get(); // 获取当前线程状态 int rs = runStateOf(c); // Check if queue empty only if necessary. /** * 这个if判断 * 如果线程池处于SHUTDOWN,STOP,TIDYING,TERMINATED的时候,则表示此时不再接收新任务; * 接着判断以下3个条件,只要有1个不满足,则返回false: * 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务 * 2. firsTask为空 * 3. 阻塞队列不为空 * * 首先考虑rs == SHUTDOWN的情况 * 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false; * 然后,如果firstTask为空,并且workQueue也为空,则返回false, * 因为队列中已经没有任务了,不需要再添加线程了 */ if (rs >= SHUTDOWN && !(rs == SHUTDOWN && // 不在接受新的任务 firstTask == null && // 队列中已经没有任务了,不需要再添加线程了 !workQueue.isEmpty())) return false; // 增加工作线程数 for (; ; ) { // 线程数量 int wc = workerCountOf(c); // 如果当前线程数大于线程最大上限CAPACITY return false // 创建核心线程则与 corePoolSize 比较,否则与 maximumPoolSize 比较 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // 尝试增加workerCount,如果成功,则跳出第一个for循环 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } // 创建一个新的线程并执行 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; // 新建线程,将线程封装成Worker w = new Worker(firstTask); // 每一个Worker对象都会创建一个线程 final Thread t = w.thread; if (t != null) { // 将任务添加到workers Queue中 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); // 线程池状态 int rs = runStateOf(c); // rs < SHUTDOWN表示是RUNNING状态; if (rs < SHUTDOWN || // rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。 // 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务 (rs == SHUTDOWN && firstTask == null)) { // 当前线程已经启动,抛出异常 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // workers是一个HashSet<Worker> workers.add(w); // 设置最大的池大小largestPoolSize,workerAdded设置为true int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } // 启动线程 if (workerAdded) { // 启动时会调用Worker类中的run方法,Worker本身实现了Runnable接口,所以一个Worker类型的对象也是一个线程 t.start(); workerStarted = true; } } } finally { // 线程启动失败 if (!workerStarted) addWorkerFailed(w); } return workerStarted; }
Worker 类继承了 AQS并实现了 Runnable 接口,注意其中的 firstTask 和 thread 属性:
firstTask 用它来保存传入的任务;thread 是在调用构造方法时通过 ThreadFactory 来创建的线程,是用来处理任务的线程。
在调用构造方法时,需要传入任务,这里通过 getThreadFactory().newThread(this) 来新建一个线程,newThread 方法传入的参数是 this,因为 Worker 本身继承了 Runnable 接口,所以一个 Worker 对象在启动的时候会调用 Worker 类中的 run 方法。
以上是关于线程池原理的主要内容,如果未能解决你的问题,请参考以下文章