目录
(1) “直接执行” (常用的队列是 SynchronousQueue (同步队列).
(2) 使用无界队列 (使用 Integer.MAX_VALUE 作为其默认容量, 例如: 基于链表的阻塞队列 LinkedBlockingQueue).
(3) 使用有界队列 (例如: 基于数组的阻塞队列 ArrayBlockingQueue).
<5>RejectedExecutionHandler handler
1、ctl
ctl 是一个 AtomicInteger 对象,包含两部分信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount).
高3位来表示线程池的运行状态, 用低29位来表示线程池内有效线程的数量.
所以线程池理论上最大线程个数是2^29-1
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 高3位表示状态,低29位表示线程池中线程的多少
private static final int COUNT_BITS = Integer.SIZE - 3; // 32-3 = 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 左移29为减1,即最终得到为高3位为0,低29位为1的数字,作为掩码,是二进制运算中常用的方法
private static final int RUNNING = -1 << COUNT_BITS; // 高三位111
private static final int SHUTDOWN = 0 << COUNT_BITS; // 高三位000
private static final int STOP = 1 << COUNT_BITS; // 高三位001
private static final int TIDYING = 2 << COUNT_BITS; // 高三位010
private static final int TERMINATED = 3 << COUNT_BITS; // 高三位011
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } // 保留高3位,即计算线程池状态
private static int workerCountOf(int c) { return c & CAPACITY; } // 保留低29位, 即计算线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; } // 求ctl
2、状态转换
线程池一共5种状态:
① RUNNING (运行状态):
说明:能接受新提交的任务, 并且也能处理阻塞队列中的任务.
状态转换:线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
② SHUTDOWN (关闭状态):
说明:不再接受新提交的任务, 但却可以继续处理阻塞队列中已保存的任务.
状态转换:在线程池处于 RUNNING 状态时, 调用 shutdown()方法会使线程池进入到该状态.
③ STOP :
说明:不能接受新提交的任务, 也不能处理阻塞队列中已保存的任务, 并且会中断正在处理中的任务.
状态转换:在线程池处于 RUNNING 或 SHUTDOWN 状态时, 调用 shutdownNow() 方法会使线程池进入到该状态.
④ TIDYING (清理状态):
说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。(terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 )
状态转换:
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
⑤ TERMINATED :
说明:线程池彻底终止,就变成TERMINATED状态。
状态转换:terminated() 方法执行完后就进入该状态.
isShutDown
当调用shutdown()或shutdownNow()方法后返回为true。
isTerminated
当调用shutdown()方法后,并且所有提交的任务完成后返回为true;
当调用shutdownNow()方法后,成功停止后返回为true;
判断关闭后所有任务是否都已完成,若关闭后所有任务都已完成,则返回true。
注意除非首先调用shutdown或shutdownNow,否则isTerminated永不为true。
3、几个重要的参数及注意事项
<1>corePoolSize
表示的是线程池中一直存活着的线程的最小数量, 这些一直存活着的线程又被称为核心线程.
notice1:调用了allowCoreThreadTimeOut()方法并传递参数为true, 设置允许核心线程因超时而停止(terminated), 在那种情况下, 一旦所有的核心线程都先后因超时而停止了, 线程池中的核心线程数量最终会变为0
notice2:核心线程默认是按需创建并启动的,即只有当线程池接收到我们提交给他的任务后, 才会去创建并启动一定数量的核心线程来执行这些任务. 如果他没有接收到相关任务, 他就不会主动去创建核心线程. 这种默认的核心线程的创建启动机制, 有助于降低系统资源的消耗;当然也可以通过调用 prestartCoreThread() 或 prestartAllCoreThreads() 方法来改变这一机制, 使得在新任务还未提交到线程池的时候, 线程池就已经创建并启动了一个或所有核心线程, 并让这些核心线程在池子里等待着新任务的到来.
<2>maximumPoolSize
表示线程池内能够容纳线程数量的最大值.
<3>keepAliveTime
表示空闲线程处于等待状态的超时时间(也即, 等待时间的上限值, 超过该时间后该线程会停止工作).
notice1:
allowCoreThreadTimeOut=false,当非核心线程进入空闲等待时,开始计算各自的等待时间, 并用这里设定的 keepAliveTime 的数值作为他们的超时时间, 一旦某个非核心线程的等待时间达到了超时时间, 该线程就会停止工作(terminated),核心线程在这种情况下却不会受超时机制的制约, 核心线程即使等待的时间超出了这里设定的 keepAliveTime, 也依然可以继续处于空闲等待状态而不会停止工作.
notice2:
如果要执行的任务相对较多,并且每个任务执行的时间比较短,那么可以为该参数设置一个相对较大的数值,以提高线程的利用率。
如果执行的任务相对较少, 线程池使用率相对较低, 那么可以先将该参数设置为一个较小的数值, 降低系统线程资源的开销, 后续如果发现线程池的使用率逐渐增高以后, 可以自己手动调用 setKeepAliveTime(long, TimeUnit)方法来重新设定 keepAliveTime 字段的值.
<4>workQueue
workQueue 是一个内部元素为 Runnable(各种任务, 通常是异步的任务) 的阻塞队列.
(1) “直接执行” (常用的队列是 SynchronousQueue (同步队列).
这种队列内部不会存储元素. 每一次插入操作都会先进入阻塞状态, 一直等到另一个线程执行了删除操作, 然后该插入操作才会执行. 同样地, 每一次删除操作也都会先进入阻塞状态, 一直等到另一个线程执行了插入操作, 然后该删除操作才会执行.
当提交一个任务到包含这种 SynchronousQueue 队列的线程池以后, 线程池会去检测是否有可用的空闲线程来执行该任务, 如果没有就直接新建一个线程来执行该任务而不是将该任务先暂存在队列中.
“直接切换”的意思就是, 处理方式由”将任务暂时存入队列”直接切换为”新建一个线程来处理该任务”.
这种策略适合用来处理多个有相互依赖关系的任务, 因为该策略可以避免这些任务因一个没有及时处理而导致依赖于该任务的其他任务也不能及时处理而造成的锁定效果. 因为这种策略的目的是要让几乎每一个新提交的任务都能得到立即处理, 所以这种策略通常要求最大线程数 maximumPoolSizes 是无界的。否则线程池满了之后就会出现拒绝。
静态工厂方法 Executors.newCachedThreadPool() 使用了这个队列。
(2) 使用无界队列 (使用 Integer.MAX_VALUE 作为其默认容量, 例如: 基于链表的阻塞队列 LinkedBlockingQueue).
使用无界队列时线程池中能够创建的最大线程数就等于核心线程数 corePoolSize, maximumPoolSize 的数值起不到任何作用.
如果向这种线程池中提交一个新任务时发现所有核心线程都处于运行状态, 那么该任务将被放入无界队列中等待处理.
当要处理的多个任务之间没有任何相互依赖关系时, 就适合使用这种队列策略来处理这些任务.
静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。
(3) 使用有界队列 (例如: 基于数组的阻塞队列 ArrayBlockingQueue).
当要求线程池的最大线程数 maximumPoolSizes 要限定在某个值以内时, 线程池使用有界队列能够降低资源的消耗。
通常来说, 设置较大的队列容量和较小的线程池容量, 能够降低系统资源的消耗(包括CPU的使用率, 操作系统资源的消耗, 上下文环境切换的开销等), 但却会降低线程处理任务的吞吐量. 如果发现提交的任务经常频繁地发生阻塞的情况, 那么你就可以考虑增大线程池的容量, 可以通过调用 setMaximumPoolSize() 方法来重新设定线程池的容量.
<5>RejectedExecutionHandler handler
两个条件会触发拒绝策略:
① 当线程池处于 SHUTDOWN (关闭) 状态时 (不论线程池和阻塞队列是否都已满)
② 当线程池中的所有线程都处于运行状态并且线程池中的阻塞队列已满时
几种具体的拒绝策略:
① AbortPolicy:直接抛异常的处理方式, 抛出 RejectedExecutionException 异常.
② CallerRunsPolicy:将新提交的任务放在 ThreadPoolExecutor.execute()方法所在的那个线程中执行.
③ DiscardPolicy:直接不执行新提交的任务.
④ DiscardOldestPolicy 这种处理方式分为两种情况:
① 当线程池已经关闭 (SHUTDOWN) 时, 就不执行这个任务了, 这也是 DiscardPolicy 的处理方式.
② 当线程池未关闭时, 会将阻塞队列中处于队首 (head) 的那个任务从队列中移除, 然后再将这个新提交的任务加入到该阻塞队列的队尾 (tail) 等待执行.
………………
RejectedExecutionHandler 这个参数是个接口, 线程池所提供的这四种方式其实都是该接口的实现类
所以我们可以自定义一个类来实现该接口, 并在重写该接口的 rejectedExecution() 方法时提供我们自己的处理逻辑。
4、源码
将一个Runnable放到线程池执行有两种方式,一个是调用ThreadPoolExecutor#submit,一个是调用ThreadPoolExecutor#execute。其实submit是将Runnable封装成了一个RunnableFuture,然后再调用execute,最终调用的还是execute,所以我们这里就只从ThreadPoolExecutor#execute开始分析。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { // 线程数量小于coresize,那么就调用addWorker
if (addWorker(command, true))
return;
c = ctl.get();
}
// 不满足上述条件,即线程数量 >= coreSize,或者addWorker返回fasle,那么走下面的逻辑
if (isRunning(c) && workQueue.offer(command)) { // 可以看到是往blockingqueue中放task
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果不满足上述条件,即blockingqueue也放不进去,那么就走下面的逻辑
else if (!addWorker(command, false))
reject(command);
}
如果线程数量小于coresize那么就执行task,否则就放到queue中,如果queue也放不下就走下面addWorker,如果也失败了,那么就调用reject策略。