创建线程池不允许使用Executors去创建,而是使用ThreadPoolExecutor的方式,通过使用ThreadPoolExecutor的方式我们可以根据业务需要指定适合的核心线程数最大线程数,线程空闲存活时间,任务等待队列,拒绝策略,还可以指定线程名称等。
Executors创建线程池的方式内部使用的也是ThreadPoolExecutor的方式。
一、Executors创建线程池的几种方式及存在的问题
1、Executors.newSingleThreadExecutor() 使用无界队列可能导致OOM
/** * 创建一个单个工作线程的线程池,等待执行的任务存放在一个无界队列中,如果因为在关闭前的执行期间出现失败而终止了此单个线程的话,将会有一个新的线程
代理它执行后续的任务。确保任务按照顺序执行,并
且在任何时候都不会有多个任务处在活动状态即不会有多个任务同时在执行。 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } /** * 等待的任务使用无界队列,可能导致OOM * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
2、Executors.newFixedThreadPool() 使用无界队列可能导致OOM
/** * 创建一个可重用的固定线程数量的线程池,以共享无界队列方式来运行这些线程,如果在所有的线程都在活动状态提交的
任务则在有线程空闲之前在无界队列中等待。池中的线程如果没有被显示的关闭,则将一直存活。 */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } // 同样是无界队列,易导致OOM public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
3、Executors.newCachedThreadPool() 允许创建的最大线程数为2^31-1,可能导致OOM,且等待队列无效,不会使用
/** * 创建一个可根据需要创建新线程的线程池,核心线程数为0,如果有新任务提交进来,将优先重用之前创建的空闲的还未回收的线程,如果没有则创建一个新的线程来执行任务,允许创建的最大的线程数是2^31-1,因此可能会导致OOM,空闲了60秒钟的线程将被销毁。 */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
4、Executors.newScheduledThreadPool() 允许创建的最大线程数为2^31-1,可能导致OOM,且等待队列无效,不会使用
/** * 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行 */ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } ScheduledExecutorService 中提供了延迟执行和定时执行的方法
二、ThreadPoolExecutor构造器
ThreadPoolExecutor 的构造函数,定义ThreadPoolExecutor时应用static final来修饰。
// 使用默认的线程工厂和拒绝策略创建线程池 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
.
// 使用默认的拒绝策略创建线程池 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
.
// 使用默认的线程工程,指定的拒绝策略创建线程池 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
.
// 指定所有参数 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
参数说明:
1、corePoolSize 表示常驻核心线程数,如果大于0,则即使执行完任务,线程也不会被销毁。因此这个值的设置非常关键,设置过小会导致线程
频繁地创建和销毁,设置过大会造成浪费资源
2、maximumPoolSize 表示线程池能够容纳的最大线程数。必须大于或者等于1。如果待执行的线程数大于此值,需要缓存在队列中等待
3、keepAliveTime 表示线程池中的线程空闲时间,当空闲时间达到keepAliveTime值时,线程会被销毁,避免浪费内存和句柄资源。在默认情况下,当线程池
中的线程数大于corePoolSize时,keepAliveTime才起作用,达到空闲时间的线程,直到只剩下corePoolSize个线程为止。但是当ThreadPoolExecutor的
allowCoreThreadTimeOut设置为true时(默认false),核心线程超时后也会被回收。(一般设置60s)
4、TimeUnit 表示时间单位,keepAliveTime的时间单位通常是TimeUnit.SECONDS
5、workQueue 表示缓存队列。当请求的线程数大于corePoolSize(码出高效说是maximumPoolSize,个人不赞同)时,线程进入BlockingQueue阻塞队列。
6、threadFactory 表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给threadFactory增加组名前缀来实现的。在用jstack分析时,就可以知道
线程任务是由哪个线程工厂产生的。
7、handler 表示执行拒绝策略的对象。当超过workQueue的缓存上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护。友好的拒绝策略可以是如下
三种:
1):保存到数据库进行削峰填谷。在空闲时再取出来执行
2):转向某个提示页面
3):打印日志
使用自定义的线程工厂来为线程池中的线程定义有意义的名字,便于在使用jstack来排查问题时,快速定位问题来源(回溯):
public class MyThreadFactory implements ThreadFactory { /** * 线程池中的线程名称前缀 */ private String namePrefix; /** * 原子的整型 */ private AtomicInteger atomicInteger = new AtomicInteger(1); public MyThreadFactory(String whatFeaturOfGroup) { this.namePrefix = "From MyThreadFactory" + whatFeaturOfGroup + "-worker-"; } @Override public Thread newThread(Runnable r) { String name = namePrefix + atomicInteger.getAndIncrement(); Thread thread = new Thread(r, name); return thread; } }
问题?要处理的线程大于核心线程就放入缓存队列还是等大于最大线程才放入缓存队列?
目前可以确定是大于核心线程就放到缓存队列中,队列满了之后才会继续创建线程
参数设置:
具体参数设置要根据系统的并发量,每个任务的处理时间,允许的最大响应时间等来设置
1、核心线程数的设置(平均并发量,任务处理时间)
假如:系统每秒的任务数即并发量为100-1000,每个任务的处理时间为0.1秒。
则每个线程每秒可处理 1/0.1 即10个任务,那么需要的线程数为10-100个线程。那么CorePoolSize应该设置大于10
具体根据 2 8原则,即80%情况下的系统并发量,若80%情况下的系统并发量小于200,最多为1000,则CorePoolSize可设为20
即要在单位时间1s内处理80%的任务
2、等待队列大小的设置(核心线程数,任务处理时间,允许最大响应时间)
等待队列的长度要根据核心线程数,以及系统对任务的最大响应时间的要求来设置。例如:系统要求2秒内必须响应,responseTime=2
核心线程数为CorePoolSize=20,任务处理时间为taskTime=0.1s,则队列长度为CorePoolSize*taskTime*responseTime=400
3、最大线程数的设置(系统最大并发量,任务处理时间)
当系统负载达到最大值时,核心线程数已经无法按时处理完所有任务,这时就需要增加线程。每秒200个任务时,需要的线程为20个,队列的长度为400个,那么还需要1000-200-400=400个任务需要处理
400/每个任务处理时间0.1秒=40个线程,因此需要最大线程数为60个。
注意:需要关注CPU的核心数,如果线程数量太多会带来不必要的线程上下文切换开销(单线程的Redis没有上下文切换开销和锁,是其快的一部分原因)。
三、任务执行机制
1、任务调度
首先,所有的任务都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来的执行流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。
其执行过程如下:
1、首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
2、如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
3、如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
4、如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
5、如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
6、线程池中存活的线程执行完当前任务后,会在循环中反复从BlockingQueue<Runnable>队列中获取任务来执行
2、任务缓冲
任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
3、任务申请
任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。
第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。
线程需要从任务缓存模块中不断地取任务执行,帮助线程从阻塞队列中获取任务,实现线程管理模块和任务管理模块之间的通信。这部分策略由getTask方法实现,其源码如下:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
其执行流程如下图所示:
getTask这部分进行了多次判断,为的是控制线程的数量,使其符合线程池的状态。如果线程池现在不应该持有那么多线程,则会返回null值。工作线程Worker会不断接收新任务去执行,而当工作线程Worker接收不到任务的时候,就会开始被回收。
4、拒绝策略
任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximunPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。
拒绝策略是一个接口,其设计如下:
public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种现成拒绝策略,如下:
名称 | 描述 |
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。线程池默认的拒绝策略,可发现线程池的异常状态。对于关键的业务,推荐使用此策略。 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,什么都不做。此种方式无法发现线程池的异常状态。 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃队列中最前面的任务,然后重新提交被拒绝的任务。是否要采用此拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量 |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程(提交任务的线程)处理该任务。这种情况是需要让所有任务都执行完毕,那么就适合大量计算的任务类型去执行,多线程仅仅是增大吞吐量的手段,最终必须要让每个任务都执行完毕 |
1、AbortPolicy
默认的拒绝策略直接抛出异常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } /**
* Always throws RejectedExecutionException. 直接抛出异常 */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
2、DiscardPolicy 对任务不做任何处理,直接丢弃
public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardPolicy}. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
3、DiscardOldestPolicy 取出队列队首的元素,即等待时间最长的元素直接丢弃掉。然后将新任务加入到任务等待队列中。
public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardOldestPolicy} for the given executor. */ public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { // 取出队列中队首任务,不做任何处理直接丢掉 e.getQueue().poll(); e.execute(r); } } }
4、CallerRunsPolicy 由调用线程(提交任务的线程)处理该任务。这种情况是需要让所有的任务都必须执行完毕。
public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
四、设置核心线程数在空闲时是否等待
默认值是false,即核心线程数即使在没有任务时也会一直存活
如果设置为true,则核心线程数在等待keepAliveTime后就会关闭
/** * If false (default), core threads stay alive even when idle. * If true, core threads use keepAliveTime to time out waiting * for work. */ private volatile boolean allowCoreThreadTimeOut;
/** * Returns true if this pool allows core threads to time out and * terminate if no tasks arrive within the keepAlive time, being * replaced if needed when new tasks arrive. When true, the same * keep-alive policy applying to non-core threads applies also to * core threads. When false (the default), core threads are never * terminated due to lack of incoming tasks. * * @return {@code true} if core threads are allowed to time out, * else {@code false} * * @since 1.6 */ public boolean allowsCoreThreadTimeOut() { return allowCoreThreadTimeOut; }
五、生命周期管理
1、线程池运行状态维护
线程池运行状态的维护,使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量(workerCount)两个关键参数的维护放在了一起,如下代码所示:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl 这个AtomicInteger 类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段。它同时包含两部分信息:线程池的运行状态(runState)和线程池内有效线程的数量(workerCount),高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。线程池源码中,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度会快很多。
关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如以下代码所示:
// Packing and unpacking ctl 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; } // 通过状态和线程数生成ctl
ThreadPoolExecutor的运行状态有5种,分别为:
// runState is stored in the high-order bits 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 | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务 |
STOP | 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程 |
TIDYING | 所有的任务都已终止了,workerCount(有效线程数)为0 |
TERMINATED | 在terminated()方法执行完后进入该状态 |
其生命周期转换如下图所示:
七、Worker线程管理
1、线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker。
追溯其源码如下:
1)执行任务的方法
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ // 返回包含线程数及线程池状态的Integer类型数值
int c = ctl.get();
// 如果工作线程数小于核心线程数,则创建线程任务并执行 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return;
// 如果创建失败,防止外部已经在线程池中加入新任务,重新获取一下 c = ctl.get(); }
// 只有线程池处于RUNNING状态,再执行后半句置入队列 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get();
// 重新检查线程池的状态,若不是RUNNING状态,则将刚加入队列的任务移除 if (! isRunning(recheck) && remove(command)) reject(command);
// 如果之前的线程已被消费完,则新建一个线程 else if (workerCountOf(recheck) == 0) addWorker(null, false); }
// 核心池和队列都已满,尝试创建一个新线程(说明前面的疑问,工作线程达到了核心线程数后,新的任务放入队列中,队列满了之后再去创建新的线程) else if (!addWorker(command, false)) reject(command); }
2)addWorker:
根据当前线程池状态,检查是否可以添加新的任务线程,如果可以则创建并启动任务,如果一切正常则返回true,返回false的可能性如下:
1、线程池没有处于RUNNING状态
2、线程工厂创建新的任务线程失败
firstTask:外部启动线程池需要构造的第一个线程,它是线程的母体
core:新增工作线程的判断指标,解释如下:
true:表示新增工作线程时,需要判断当前RUNNING状态的线程是否少于corePoolSize
false:表示新增工作线程时,需要判断当前RUNNING状态的线程是否少于maxiMumPoolSize
private boolean addWorker(Runnable firstTask, boolean core) {
// 响应下文的continue retry,快速退出多层嵌套循环 retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c);
// 如果超过最大允许线程数则不能再添加新的线程 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
// 将当前活动线程数+1 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; w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { 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); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
3)Worker对象时工作线程的核心类实现,部分源码如下:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {/** Thread this worker is running in. Null if factory fails. */ final Thread thread; // worker持有的线程 /** Initial task to run. Possibly null. */ Runnable firstTask; // 初始化的任务,可以为null /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); } }
Worker这个工作线程,实现了Runnable接口,并持有一个线程 thread ,一个初始化的任务 firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;
firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就是对应核心线程创建时的情况;如果这个值
是null,那么就需要创建一个线程去执行任务列表(workerQueue)中的任务,也就是非核心线程的创建。
Worker线程执行任务的模型如下图所示:
线程池需要管理线程的生命周期,需要在线程长时间不运行的时候进行回收。线程池使用一张hash表去持有线程的引用,这样可以添加引用、移除引用这样的操作来控制线程的生命周期。
这个时候重要的就是如何判断线程是否在运行。
Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反映线程现在的执行状态。
1、Worker线程每次执行任务都先获取锁,lock() 方法一旦获取了独占锁,表示当前线程正在执行任务中
2、如果正在执行任务,则不应该中断线程
3、Worker线程每次执行完任务都会释放锁,如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。
4、线程池在执行shutdown方法或tryTerminate方法时会调用 interruptIdleWorkers() 方法来中断空闲的线程,interruptIdleWorkers() 方法会使用tryLock方法来判断线程池中的线程是否
是空闲状态,如果是空闲状态,则可以安全回收。源码如下:
private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { // 尝试获取锁 try { t.interrupt(); // 中断线程 } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
线程回收过程如下图所示:
2、Worker线程增加
增加线程时通过线程池中的addWorker()方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。
addWorker方法有两个参数:firstTask、core。
firstTask用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true表示在新增线程时会判断当前活动线程是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,
其执行流程如下图所示:
3、Worker线程回收
线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。
Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。
源码:
Worker: public void run() { runWorker(this); }
runWorker(this): try { while (task != null || (task = getTask()) != null) { //执行任务 } } finally { processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己 }
线程回收的工作是在processWorkerExit方法完成的。
事实上,在这个方法中,将线程引用移出线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程。
4、Worker线程执行任务
在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的执行过程如下:
1、while循环不断地通过getTask()方法获取任务。
2、getTask()方法从阻塞队列中取任务。
3、如果线程池正在停止,那么要保证当前线程是中断状态;否则要保证当前线程不是中断状态。
4、执行任务。
5、如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。
执行流程如下图所示:
八、总结
总结使用线程池需要注意以下几点:
1、合理设置各类参数,应根据实际业务场景来设置合理的工作线程数
2、线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
3、创建线程或线程池请指定有意义的线程名称,方便出错时回溯
4、线程池不允许使用Executors,而是通过ThreadPoolExecutor的方式来创建,这样的处理方式能更加明确线程池的运行规则,规避资源耗尽的风险。
一个线程池中的线程异常了,那么线程池会怎么处理这个线程?
1、当执行方式是execute时,可以看到异常堆栈异常的输出
2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常
3、线程池会把这个线程从线程池里移除掉,并创建一个新的线程放到线程池中
4、不会影响线程池里面其他线程的正常执行
源码层面分析:
TODO