1. 构造方法

构造方法中有7大参数,我们一个参数一个参数来分析
1.1 核心线程数corePoolSize
//Set containing all worker threads in pool. Accessed only when holding mainLock.
private final HashSet<Worker> workers = new HashSet<Worker>();
public int getPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
return runStateAtLeast(ctl.get(), TIDYING) ? 0
: workers.size();
} finally {
mainLock.unlock();
}
}
corePoolSize核心线程数,类似配置,是个固定值。
getPoolSize()返回的是线程池里面存放的线程。我们先看下源码,这是一个同步方法,返回的是workers.size()。从注释可以看出这个集合存储的是当前线程池所有工作的线程池。
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
System.out.println("初始化线程池中的线程数量:"+pool.getPoolSize());
}
输出结果:
初始化线程池中的线程数量:0
刚初始化的线程池里的线程数量为0
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
System.out.println("初始化线程池中的线程数量:"+pool.getPoolSize());
for(int i=0;i<3;i++){
pool.execute(() -> {
try{
Thread.sleep(100);
}catch (Exception e){}
});
System.out.println("线程池线程数量:"+pool.getPoolSize());
}
Thread.sleep(10000);
System.out.println("最终线程数量:"+pool.getPoolSize());
}
输出结果:
初始化线程池中的线程数量:0
线程池线程数量:1
线程池线程数量:2
线程池线程数量:2
最终线程数量:2
线程池的大小始终没有超过2,在子线程都执行完了之后也就是任务都执行完了,线程池的大小还是2,并没有变成0。那是不是始终不会消亡呢?再看下面代码,多设置一个参数pool.allowCoreThreadTimeOut(true)。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,1000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.allowCoreThreadTimeOut(true);
for(int i=0;i<3;i++){
pool.execute(() -> {
try{
Thread.sleep(100);
}catch (Exception e){}
});
System.out.println("线程池线程数量:"+pool.getPoolSize());
}
System.out.println("超时之前线程数量:"+pool.getPoolSize());
Thread.sleep(10000);
System.out.println("超时之后线程数量:"+pool.getPoolSize());
}
线程池线程数量:1
线程池线程数量:2
线程池线程数量:2
超时之前线程数量:2
超时之后线程数量:0
可以看到上面的代码在子线程都运行结束后,线程池的数量变成了0。allowCoreThreadTimeOut方法的作用是:设置核心线程池是否可以超时终止策略,true可以超时终止,false不终止。如果设置为true,那核心线程池的超时时间就是KeepAliveTime,并且此时要求KeepAliveTime必须要大于0。
以上代码还有一处值得思考,放入的任务超过了核心线程数2,最大线程数又是设置的5,为什么没有扩容呢?我们继续往下看。
1.2 最大线程数maximumPoolSize
最大线程数,顾名思义就是当前线程池能同时工作的最大任务数,那什么时候会从核心线程数扩容到最大线程数呢?
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<7;i++){
pool.execute(() -> {
try{
Thread.sleep(100);
}catch (Exception e){}
});
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
}
Thread.sleep(100);
System.out.println("超时之前线程数量:"+pool.getPoolSize());
Thread.sleep(10000);
System.out.println("超时之后线程数量:"+pool.getPoolSize());
}
输出结果:
当前放入线程池任务数1线程池线程数量:1
当前放入线程池任务数2线程池线程数量:2
当前放入线程池任务数3线程池线程数量:2
当前放入线程池任务数4线程池线程数量:2
当前放入线程池任务数5线程池线程数量:3
当前放入线程池任务数6线程池线程数量:4
当前放入线程池任务数7线程池线程数量:5
超时之前线程数量:5
超时之后线程数量:2
从上面代码运行的输出结果可以看出,当放入线程池的任务数量变成5的时候,线程池开始扩容。那为什么是放第五个任务的时候扩容的呢?这里的核心线程数是2,(阻塞的工作队列)workQueue设置的也是2,也就是说当这两个都满了之后才会扩容。从最终的线程数可以看出,扩容的线程数,在任务结束后会自动消亡。
1.3 存活时间keepAliveTime
这个参数比较简单,空闲的线程等待work的时间,超过这个时间没有work,扩容的线程池会超过该时间会消亡,如果设置allowCoreThreadTimeOut为true,那么核心线程池也会消亡。
/**
* Timeout in nanoseconds for idle threads waiting for work.
* Threads use this timeout when there are more than corePoolSize
* present or if allowCoreThreadTimeOut. Otherwise they wait
* forever for new work.
*/
private volatile long keepAliveTime;
1.4 存活时间单位TimeUnit
线程池内部运行都是通过纳秒来进行,对外使用者可以自己定义时间单位,算是一种友好交互吧。

1.5 阻塞队列BlockingQueue
从上面可知,当核心线程池满了后,新的任务工作将会放到阻塞队列中去。下面我们来看一下java提供了哪些队列,队列的具体实现就不详细说明了,后面单独记录一篇。
1.5.1 ArrayBlockingQueue
ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。队列构造方法需要指定队列长度(capacity)和进出队列顺序(fair),有点类似公平锁和非公平锁。如果为true的话ArrayBlockingQueue 内部以 (先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。否则出队列的顺序是不保证的。
/**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if {@code true} then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if {@code false} the access order is unspecified.
* @throws IllegalArgumentException if {@code capacity < 1}
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
那么问题来了,当数组队列满了之后,新进来的任务是直接扩容执行,还是将队列头部的任务取出来执行,然后将新任务放到队列尾部中去呢?
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<7;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
Thread.sleep(10000);
System.out.println("第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
}
}
输出结果:
当前放入线程池任务数1线程池线程数量:0
当前放入线程池任务数2线程池线程数量:1
当前放入线程池任务数3线程池线程数量:2
当前放入线程池任务数4线程池线程数量:2
当前放入线程池任务数5线程池线程数量:2
当前放入线程池任务数6线程池线程数量:3
当前放入线程池任务数7线程池线程数量:4
第5个线程执行了
第2个线程执行了
第1个线程执行了
第6个线程执行了
第7个线程执行了
第4个线程执行了
第3个线程执行了
上面的运行结果可以看出3,4线程被放入阻塞队列中的,最后才被执行了。
1.5.2 LinkedBlockingQueue
一个有界阻塞队列,如果创建的时候不指定队列长度默认队列长度为Integer.MAX_VALUE。
基于链表实现的队列,不能保证出队列的先后顺序。
1.5.3 PriorityBlockingQueue
一个由数组结构组成的具有优先级的无界阻塞队列,优先级通过参数Comparator实现。默认队列长度是11,不同于ArrayBlockingQueue的长度是固定的,该队列的是可以动态扩容的。当然虽然说是无界,但是受JVM内存限制,不可无线大,会导致OOM。有任务要执行,可以对任务加一个优先级的权重,这样队列会识别出来,对该任务优先进行出队。
1.5.4 SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,0, TimeUnit.MILLISECONDS,
new SynchronousQueue<>(), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<5;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
Thread.sleep(10000);
System.out.println("第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
System.out.println("当前线程池数量"+pool.getPoolSize());
}
}
输出结果:
当前放入线程池任务数1线程池线程数量:0
当前线程池数量1
当前放入线程池任务数2线程池线程数量:1
当前线程池数量2
当前放入线程池任务数3线程池线程数量:2
当前线程池数量3
当前放入线程池任务数4线程池线程数量:3
当前线程池数量4
当前放入线程池任务数5线程池线程数量:4
当前线程池数量5
第3个线程执行了
第4个线程执行了
第1个线程执行了
第2个线程执行了
第5个线程执行了
使用SynchronousQuene队列,线程并没有被存储,当有超过核心线程池的任务,直接扩容了。
1.6 线程工厂ThreadFactory
用于生产线程的方法,定义了一个接口,支持我们自己扩展,源码也比较简单。
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
我们可以自己实现这个接口,也可以使用Executors中默认的线程工厂。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
1.7 拒绝策略RejectedExecutionHandler
基于上面讨论的,我们还有一个问题要讨论,如果队列满了,线程池也满了,这个时候还有新任务进来会发生什么呢?
这里会执行拒绝策略,和线程工厂一样,定义了一个接口我们可以自己实现策略,当然默认也提供了4中策略。
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
1.7.1 AbortPolicy
对于超过线程池最大任务后,直接抛出异常。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
拒绝策略,当线程池和队列都满了之后,新任务进来会报错。但是不影响已经放入的线程执行完毕。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
2,1, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<4;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
Thread.sleep(1000);
System.out.println("第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
}
try{
Thread.sleep(10000);
}catch (Exception e){}
pool.execute(()->{
System.out.println("异常之后线程池还可以执行任务吗");
});
}
当前放入线程池任务数1线程池线程数量:0
当前放入线程池任务数2线程池线程数量:1
当前放入线程池任务数3线程池线程数量:1
当前放入线程池任务数4线程池线程数量:2
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.joodroid.web.controller.business.MyArrayList$$Lambda$1/125993742@e2d56bf rejected from java.util.concurrent.ThreadPoolExecutor@244038d0[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.joodroid.web.controller.business.MyArrayList.main(MyArrayList.java:16)
第3个线程执行了
第1个线程执行了
第2个线程执行了
这里我们最多能容纳3个线程(活动任务2,队列任务1),当放入第四个的时候报错了。并且当我们现有任务执行完后,在异常后再放入新任务的时候已经不再执行,所以实际使用过程中尽量避免线程池报错,包括业务代码报错需要手动处理,否则后面正常的业务也不能够被执行。
1.7.2 CallerRunsPolicy
源码逻辑比较简单,当线程池未关闭,则直接执行当前任务得run方法。否则直接丢弃了。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
最大线程池2,队列1,当放入得任务数量大于3了,后续得线程任务都是由线程调用者自己直接执行任务。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
2,1, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for(int i=0;i<5;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
if(j<4){
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
}
}
运行结果:
当前放入线程池任务数1线程池线程数量:0
当前放入线程池任务数2线程池线程数量:1
当前放入线程池任务数3线程池线程数量:1
当前放入线程池任务数4线程池线程数量:2
main第4个线程执行了
当前放入线程池任务数5线程池线程数量:2
main第5个线程执行了
pool-1-thread-2第3个线程执行了
pool-1-thread-1第1个线程执行了
pool-1-thread-2第2个线程执行了
1.7.3 DiscardPolicy
源码逻辑更简单,就是啥也不干,就当没任务放进来。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
如下执行结果,任务4和5被当前线程池抛弃了。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
2,1, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
for(int i=0;i<5;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
if(j<4){
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
}
Thread.sleep(10000);
System.exit(-1);
}
输出结果:
当前放入线程池任务数1线程池线程数量:0
当前放入线程池任务数2线程池线程数量:1
当前放入线程池任务数3线程池线程数量:1
当前放入线程池任务数4线程池线程数量:2
当前放入线程池任务数5线程池线程数量:2
pool-1-thread-2第3个线程执行了
pool-1-thread-1第1个线程执行了
pool-1-thread-2第2个线程执行了
Disconnected from the target VM, address: '127.0.0.1:58073', transport: 'socket'
1.7.4 DiscardOldestPolicy
将队列中第一元素弹出,将新任务加入到线程池中。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
第3,4个任务被抛弃了。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
2,1, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for(int i=0;i<5;i++){
int j = i;
System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
pool.execute(() -> {
try{
if(j<4){
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
}catch (Exception e){}
});
}
Thread.sleep(10000);
System.exit(-1);
}
输出结果:
当前放入线程池任务数1线程池线程数量:0
当前放入线程池任务数2线程池线程数量:1
当前放入线程池任务数3线程池线程数量:1
当前放入线程池任务数4线程池线程数量:2
当前放入线程池任务数5线程池线程数量:2
pool-1-thread-2第3个线程执行了
pool-1-thread-1第1个线程执行了
pool-1-thread-2第5个线程执行了
2.常量参数得定义
int型变量一共有32位,线程池的五种状态runState至少需要3位来表示,故workCount只能有29位。
TERMINATED(011) > TIDYING(010) > STOP(001) > SHUTDOWN(000)> RUNNING(101)
2.1 COUNT_BITS
一个常量,用于后续计算得一个常用值,大小是29。
private static final int COUNT_BITS = Integer.SIZE - 3;
2.2 RUNNING
线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
-2的29次方。(首位1表示负数)
1010 0000 0000 0000 0000 0000 0000 0000,高3位,101表示运行状态
private static final int RUNNING = -1 << COUNT_BITS;
2.3 SHUTDOWN
线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
0,不理解为啥还要左移,直接定义成0不好吗?,高3位,000表示SHUTDOWN状态
0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
2.4 STOP
线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
2的29次方。
0010 0000 0000 0000 0000 0000 0000 0000,高3位,001表示STOP状态
private static final int STOP = 1 << COUNT_BITS;
2.5 TIDYING
当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
2的30次方
0100 0000 0000 0000 0000 0000 0000 0000****高3位010
private static final int TIDYING = 2 << COUNT_BITS;
2.6 TERMINATED
线程池彻底终止,就变成TERMINATED状态。
2的30次方加上2的29次方
0110 0000 0000 0000 0000 0000 0000 0000****高3位011
private static final int TERMINATED = 3 << COUNT_BITS;
2.6 CAPACITY
0001 1111 1111 1111 1111 1111 1111 1111,低29位表示线程池最大数量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
2.7 ctl
包含了状态和线程数量,高三位是状态,低29位置是数量,原子常量,ctlOf方法是两个数的或运算,所以相当于初始化状态为RUNNING和设置初始化线程数为0,相当于加法运算。
1010 0000 0000 0000 0000 0000 0000 0000
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
3.execute方法
这里已添加第一个线程任务为例。
public void execute(Runnable command) {
//判断入参不能为空,否则报空指针错误
if (command == null)
throw new NullPointerException();
//获取当前线程池状态,ctl值为:1010 0000 0000 0000 0000 0000 0000 0000
int c = ctl.get();
//取ctl的低29位即正在运行的线程,判断当前线程池的工作任务数是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
/*将当前任务添加到现有任务中,第二个参数表示是否使用核心线程池去运行(true表示是,false表示否),如果添加成功则直接返回结束当前方法。
*addWorker我们后面分析。
*/
if (addWorker(command, true))
return;
c = ctl.get();
}
/*第一个判断当前线程池是不是运行状态即当前c的值是否小于0,第二个判断当前任务能不能成功的放入队列中。当前c的值为一个负数,小于0所以
* isRunning(c)的结果为true,后面的结果取决于新建线程时的入参队列。
*/
if (isRunning(c) && workQueue.offer(command)) {
//如果线程池是运行状态,并且能成功放入队列中,再获取一次线程池状态
int recheck = ctl.get();
//二次检查当前线程池的状态是不是运行,避免上面放入队列的过程中,线程状态改变,如果线程停止运行并且移除队列成功就执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
/*这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在
*执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池
*在RUNNING状态下必须要有一个线程来执行任务
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//线程池非运行状态,或者运行状态下放入队列失败,那就使用非核心线程执行当前任务
else if (!addWorker(command, false))
//如果非核心线程不能执行当前任务,则执行拒绝策略
reject(command);
}
看完代码画个图加深一下印象。

4.addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
//跳出循环的标识,可以break跳出多重循环,或者指定continue的位置
retry:
// 死循环执行逻辑。确保多线程环境下在预期条件下退出循环。
for (;;) {
//取出线程池状态和线程数量信息
int c = ctl.get();
//只取高3位,也就是只获取线程池状态
int rs = runStateOf(c);
//rs是当前线程池的状态,只有运行状态是负数,其它状态均非负,那这里也就说只要不是线程池不是运行状态,就返回false。
//在线程池状态不是运行的前提下:如果线程池状态是SHUTDOWN,传入的任务为空,或者队列不是空,三者同时满足的情况下才会继续后面的流程
//那这里为什么会允许添加空的任务呢?我们继续往下看(上面调用addWorker也有说明)。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//获取正在运行的线程数
int wc = workerCountOf(c);
//大于等于最大线程数返回fasle,大于等于核心线程数(入参要求使用核心线程去运行的情况下)或者最大线程数返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//使用cas,将工作线程数加1,如果成功跳出循环
if (compareAndIncrementWorkerCount(c))
break retry;
//任务数cas加1失败,重新获取一次线程状态
c = ctl.get(); // Re-read ctl
//如果状态已改变,重新执行外层循环,如果没改变执行内部循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
/* 经过以上循环分析基本可以得出,外层循环用于线程池状态判断,而内存循环用于是否能成功添加任务。
* 代码能执行到这,说明ctl中线程数已经加1了,现在我们需要做的就是把真正的任务加到线程池中去
*/
//定义任务开始标志
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());
//如果线程为运行状态,或者SHUTDOWN状态下,任务为空则将当前work添加到线程池中的workes集合中
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();
}
//添加成功则启动当前线程,并将启动状态置为true
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//启动失败(或者添加任务失败),
if (! workerStarted)
//后面看这个方法
addWorkerFailed(w);
}
return workerStarted;
}
5.addWorkerFailed方法
能进入这个代码,前提是执行了addWorker的部分逻辑,需要结合addWorker一起看。什么情况下会出现这种问题呢?在成功增加活跃线程数后并成功new Worker后,线程池状态改变为 > SHUTDOWN,既不可接受新任务,又不能执行任务队列剩余的任务,此时线程池应该直接停止。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
//上锁
mainLock.lock();
try {
if (w != null)
//从队列中移除添加失败的任务,当然之前不一定放进去了,就算没放进去,做一次移除也没关系
workers.remove(w);
//addWorker的逻辑已经给任务数加1了,这里做CAS减法操作
decrementWorkerCount();
//看方法名,试图将线程池置为TERMINATED状态,我们接下来看这个方法
tryTerminate();
} finally {
//解锁
mainLock.unlock();
}
}
6.tryTerminate方法
让线程池进入TERMINATED状态。
final void tryTerminate() {
for (;;) {
//获取当前线程池状态
int c = ctl.get();
//以下三个条件,满足其一都将直接返回
//如果是运行状态,直接返回
//如果是TIDYING和TERMINATED状态,直接返回
//如果是SHUTDOWN状态,并且队列不是空的,直接返回
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//工作线程数不等于0
if (workerCountOf(c) != 0) { // Eligible to terminate
//如果活跃线程数不为0,中断所有的worker线程,后面详细说明这个方法
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 首先通过 CAS 将 ctl 改变成 (rs=TIDYING, wc=0),因为经过上面的判断保证了当先线程池能够达到这个状态。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 钩子函数,在ThreadPoolExecutor 中该方法是空的,用户可以通过继承 ThreadPoolExecutor 实现自定义的方法。
terminated();
} finally {
// 将 ctl 改变成 (rs=TERMINATED, wc=0),此时线程池将关闭。
ctl.set(ctlOf(TERMINATED, 0));
//最后调用 termination.signalAll 激活因调用条件变量 termination 的 await 系列方法而被阻塞的所有线程,唤醒后得知线
//程池状态为TERMINATED后也会退出。
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
7.interruptIdleWorkers方法
中断线程中的任务,如果入参为true表示只中断一个。代码逻辑比较简单
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
//上全局锁
mainLock.lock();
try {
//循环当前线程池中所有的任务
for (Worker w : workers) {
Thread t = w.thread;
//如果任务中的线程不是中断状态,并且可以成功获取worker的线程锁,那就中断线程。
//也就是说获取不到worker线程的锁,就不会中断,那什么情况下能成功获取锁呢?后面我分析worker的时候再来看
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
//释放worker线程锁
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
//解全局锁
mainLock.unlock();
}
}
8.interruptWorkers方法
interruptWorkers本身逻辑不复杂,就是上锁后循环中断任务。核心逻辑在interruptIfStarted方法中,我们继续往下看
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
这是work内部的一个方法。
void interruptIfStarted() {
Thread t;
//这里判断只要worker是运行状态,并且线程不为空没有被打断就中断当前线程,这里并没有interruptIdleWorkers方法中
//尝试获取锁成功才去打断。
// state >= 0表示worker已经启动
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
9.shutdown&shutdownNow方法
shutdown: 关闭线程池中所有空闲Worker线程,改变线程池状态为SHUTDOWN。
shutdownNow:关闭线程池中所有Worker线程,改变线程池状态为STOP,并返回所有正在等待处理的任务列表。
前面也说过了SHUTDOWN和STOP的区别,前者会执行完队列中任务,而后者既不允许接受新任务,也不允许执行剩余的任务,因此需要关闭所有Worker线程,包括正在运行的。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
//上全局锁
mainLock.lock();
try {
//校验权限,一般默认情况下jvm不开启安全校验
checkShutdownAccess();
//CAS将线程池状态设置成SHUTDOWN
advanceRunState(SHUTDOWN);
//上面已经分析过了,主要区别一下下面shutdownNow中使用的interruptWorkers方法
interruptIdleWorkers();
//ThreadPoolExecutor为空
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
//解锁
mainLock.unlock();
}
//尝试将线程状态设置为TERMINATED
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//设置线程状态为STOP
advanceRunState(STOP);
//注意和interruptIdleWorkers方法的区别
interruptWorkers();
//获取队列中剩余的所有任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
10. 小小总结一下
上述interruptIdleWorkers,interruptWorkers,shutdown,shutdownNow等方法的核心逻辑在Worker对象中,以及分析到现在任务是如何执行的,以及队列中的任务是什么时候执行的都没有涉及到。还有被打断的任务什么时候被移出worker列表也没体现。接下来我们看内部Worker对象。
11. 内部内Worker
11.1 类定义
当前类继承了AQS,并实现了Runnable接口。Worker实现了Runnable接口,说明Worker是一个任务;Worker又继承了AQS,说明Worker同时具有锁的性质,但Worker并没有像ReentrantLock等锁工具使用了CLH的功能,因为线程池中并不存在多个线程访问同一个Worker的场景,这里只是使用了AQS中状态维护的功能。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
11.2 构造方法
每个Worker对象会持有一个工作线程 thread,在Worker初始化时,通过线程工厂创建该工作线程并将自己作为任务传入工作线程当中。因此,线程池中任务的运行其实并不是直接执行提交任务的run()方法,而是执行Worker中的run()方法,在该方法中再执行提交任务的run()方法。我们接下来先看Worker的run方法。
Worker(Runnable firstTask) {
//继承的队列中的方法,该状态表示当前work还未开始工作
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//通过当前类创建线程对象,并给内部线程属性赋值
this.thread = getThreadFactory().newThread(this);
}
11.3 Worker的run方法
特别注意Worker的run方法和传入的task中run方法的区别,task的run方法才是我们真正的实现业务的方法。
worker的run方法很简单,托全局的runWorker()方法执行具体逻辑。
public void run() {
runWorker(this);
}
12 runWorker方法
死循环从队列中取出task依次执行,后面我们再看什么情况下可以从队列中取出,处理当前的worker。
final void runWorker(Worker w) {
//获取当前线程
Thread wt = Thread.currentThread();
//复制提交的任务,并将 Worker 中的 firstTask 置为 null,便于下一次重新赋值。
Runnable task = w.firstTask;
//将work中的task置空
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//这里循环获取task,我们后面看task的获取逻辑
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//空实现,可以继承ThreadPoolExecutor来实现该方法,在执行task之前,统一执行某些逻辑
beforeExecute(wt, task);
Throwable thrown = null;
try {
//task的run方法并不是通过start方法去执行线程来调用的,而是直接调用的
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 {
//空实现,可以继承ThreadPoolExecutor来实现该方法
afterExecute(task, thrown);
}
} finally {
//便于垃圾回收
task = null;
//当前worker处理的总task加1
w.completedTasks++;
//解锁,当前work的锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
//能执行到这,说明当前得worker不需要了
processWorkerExit(w, completedAbruptly);
}
}
13 processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//这里为true只有一种情况,那就是执行worker得时候异常了,将worker得计数减一
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//将当前worker完成得任务量,汇总到总任务计数中
completedTaskCount += w.completedTasks;
//从woerker集合中移除
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
//线程池状态还能够继续处理任务
if (runStateLessThan(c, STOP)) {
//如果worker正常结束
if (!completedAbruptly) {
//是否允许核心线程池超时,允许得话最小得线程数就0,否则为核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果是0,判断任务队列中是否有任务,如果有就赋值为1,表示队列中得任务至少需要一个线程来执行
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果正在工作得队列大于这个最小值(0或者核心线程池),那就直接结束当前方法,否则执行下面的addWorker增加
//线程
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//非正常结束得,添加一个新得worker到集合中
addWorker(null, false);
}
}
14 getTask方法-重要
能保持线程池中的线程一直存活的原理在这个方法中,以及非线程池的超时和核心线程池的超时都在这个方法中实现的。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果线程池状态为不能够再处理任何任务得状态,则将工作线程减1,这个时候返回null,这时runworker得循环就结束了
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取工作线程数
int wc = workerCountOf(c);
//允许超时消亡或者大于核心线程数,当前值则为true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//工作线程大于最大线程,当前worker肯定要消亡得
//工作线程大于核心线程,当前worker执行完自己的task后也会消亡,也就是说非核心线程不会处理队列中得task
//小于核心线程(核心线程池不超时消亡allowCoreThreadTimeOut=false),那就去队列中获取task
//小于核心线程(核心线程池超时消亡allowCoreThreadTimeOut=true),有其它存活的worker或者队列为空,那么当前worker消亡
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//如果worker减1成功就返回null,否则继续循环
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
* 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。所以如果核心线程池以满,新进来的task会先放到队列中去
* 然后核心线程池去队列中取task进行处理。
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
文章详细分析了Java中的ThreadPoolExecutor线程池的构造方法,包括核心线程数、最大线程数、存活时间、阻塞队列的选择、线程工厂和拒绝策略等参数的作用及示例代码。此外,还探讨了线程池的状态转换、任务执行流程以及不同阻塞队列的特点。
941

被折叠的 条评论
为什么被折叠?



