一、概述:
1、背景介绍
是ExecutorSerivce接口的具体实现。ThreadPoolExecutor使用线程池中的一个线程来执行给定的任务(Runnable或者Runnable)。是用来处理异步任务的一个接口,可以将其理解成为一个线程池和一个任务队列,提交到 ExecutorService 对象的任务会被放入任务队或者直接被线程池中的线程执行。java.util.concurrent.ThreadPoolExecutor 继承关系:
public interface Executor
public interface ExecutorService extends Executor
AbstractExecutorService implements ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
2、ThreadPoolExecutor池的处理流程:
当一个线程进入线程池之后,会进行如下的处理步骤:
(1)首先查看核心线程池是否满,当运行的线程数小于corePoolSize的时候 ,创建新的线程即Worker执行提交的任务
(2)如果线程数大于等于corePoolSize的时候,将任务提交到workQueue队列中 ,如果成功添加 ,runWorker就会执行调用了,当然这里会重新的核查此时的线程数,看下是否有线程减少,如果减少,则创建新的线程来使线程数维持在corePoolSize的数目
(3)如果队列满了后,则创建新的线程来执行,当然这里有一种极端情况,当线程数等于maximumPoolSize时,并且workQueue也满了后,就会抛出错 误 org.springframework.core.task.TaskRejectedException,另外MaxPoolSize的设定如果比系统支持的线程数还要大时,会抛出java.lang.OutOfMemoryError: unable to create new native thread 异常。
二、 构造函数
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
1.1、概念
核心线程池容量大小。线程组保留的最小线程数,如果线程组中的线程数少于此数目,则创建。指定了线程池中的线程数量,决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;线程进行核心线程池即可等待调度执行;
1.2、原则
核心线程默认不会被销毁,但是可以额外设置allowCoreThreadTimeOut(true)使得核心线程在超过keepAliveTime之后会销毁。
1.3、大小
线程池线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可
/**
* Nthreads=CPU数量
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
* W/C=任务等待时间与任务计算时间的比率
*/
Nthreads = Ncpu*Ucpu*(1+W/C)
maximumPoolSize设置成corePoolSize的两倍(按需)。
2、maximumPoolSize:
指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。通过这个来判断线程池是否已满。MaximumPoolSize = CorePoolSize + WorkQueue + 临时线程池大小
3、BlockingQueue<Runnable> workQueue阻塞队列:
关于线程池中为什么使用阻塞队列:队列是先进先出的。当放入一个元素的时候,会放在队列的末尾,取出元素的时候,会从队头取。则当队列为空或者队列满的时候怎么办呢?阻塞队列会自动帮我们处理这种情况:当阻塞队列为空的时候,从队列中取元素的操作就会被阻塞;当阻塞队列满的时候,往队列中放入元素的操作就会被阻塞,而后一旦空队列有数据了,或者满队列有空余位置时,被阻塞的线程就会被自动唤醒。这样无需关心线程何时被阻塞、何时被唤醒,一切都由阻塞队列自动帮我们完成,我们只需要关注具体的业务逻辑就可以了。
无法进入核心线程池的线程将进入任务队列等待进入池中;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;阻塞队列对象,一般需要设定容量大小。
根据前面提到的阻塞队列的线程安全实现(同步、异步),有以下几种实现类:
3.1、直接提交队列SynchronousQueue:
希望提交的任务尽快分配线程执行建议使用此。例:
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.execute(new DemoTask());
}
}
}
package exceldemo;
public class DemoTask implements Runnable{
public DemoTask() {
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
输出:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@63961c42 rejected from java.util.concurrent.ThreadPoolExecutor@65b54208[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at exceldemo.Demo.main(Demo.java:15)
pool-1-thread-1
pool-1-thread-2
可以看到,当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。即,使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
3.2、有界的任务队列ArrayBlockingQueue
使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于等于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
demo:
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.execute(new DemoTask());
}
输出:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
如果任务数过多:
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<13;i++) {
pool.execute(new DemoTask());
}
也会报错:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@5674cd4d rejected from java.util.concurrent.ThreadPoolExecutor@63961c42[Running, pool size = 2, active threads = 2, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at exceldemo.Demo.main(Demo.java:20)
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
3.3、 无界的任务队列 LinkedBlockingQueue
对于需要保证所有提交的任务都要被执行的情况建议使用此。 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<13;i++) {
pool.execute(new DemoTask());
}
输出:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
任务数过多也不会报错。
3.4、优先任务队列 PriorityBlockQueue
优先任务队列通过PriorityBlockingQueue实现,PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
package exceldemo;
public class DemoTask implements Runnable,Comparable<DemoTask>{
private int priority;
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public DemoTask() {
}
public DemoTask(int priority) {
this.priority = priority;
}
//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
@Override
public int compareTo(DemoTask o) {
return this.priority>o.priority?-1:1;
}
@Override
public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列
Thread.sleep(1000);
System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
//优先任务队列
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask(i));
}
}
}
输出:
priority:0,ThreadName:pool-1-thread-1
priority:9,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-1
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-1
priority:5,ThreadName:pool-1-thread-1
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-1
priority:1,ThreadName:pool-1-thread-1
可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。
4、keepAliveTime:
当线程池中非核心线程空闲时间超过keepAliveTime时,多余的线程会在多长时间内被销毁。
默认情况下,当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池的线程数大不于corePoolSize;不过,上面也提到了可以设置allowCoreThreadTimeOut(true)使得核心线程也能在超过keepAliveTime之后被销毁。
5、unit:
keepAliveTime的单位,为枚举,配合线程存活时间使用;
6、threadFactory:
线程工厂,用于创建线程,一般用默认即可;
7、handler:
拒绝策略;当任务太多来不及处理时,如何拒绝任务;
package exceldemo;
public class DemoTask implements Runnable{
public DemoTask() {
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,就需要你指定合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略及举例如下:
7.1、抛出异常AbortPolicy策略(默认策略):
该策略会直接抛出异常RejectedExecutionException
,阻止系统正常工作;
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@5674cd4d rejected from java.util.concurrent.ThreadPoolExecutor@63961c42[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
pool-1-thread-2
pool-1-thread-1
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
pool-1-thread-2
pool-1-thread-1
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
pool-1-thread-2
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
pool-1-thread-2
at exceldemo.Demo.main(Demo.java:19)
pool-1-thread-1
7.2、由调用线程处理CallerRunsPolicy策略:
如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
main
main
main
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
7.3、丢弃最旧任务DiscardOledestPolicy策略:
该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
7.4、丢弃任务DiscardPolicy策略:
直接丢弃任务,但是不抛出异常。该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
7.5、自定义拒绝策略:
以上内置的策略均实现了RejectedExecutionHandler接口,当然也可以自己扩展RejectedExecutionHandler接口,自定义拒绝策略,如:
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
exceldemo.DemoTask@5674cd4d执行了拒绝策略
exceldemo.DemoTask@63961c42执行了拒绝策略
exceldemo.DemoTask@65b54208执行了拒绝策略
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
三、ThreadPoolExecutor提交任务方法:
1、execute方法
无返回值的任务使用public void execute(Runnable command) 方法提交,子线程可能在主线程结束之后结束;
2、
submit方法
有返回值的任务使用public <T> Future<T> submit(Callable) 方法提交,因为提交任务后有个取数据的过程,在从
Future取数据的过程中,Callable自带的阻塞机制,这个机制保证主线程一定在子线程结束之后结束。反之如果没有取数据,子线程可能会在主线程结束之后才结束。
submit既能提交Runnable类型任务也能提交Callable类型任务。
注意:submit的返回值为Future<?>,可通过get获取其泛型。Future<?>对象.get()方法具有阻塞当前线程的做用(也就是谁调用Future<?>对象.get()就阻塞谁,防止主线程在拿返回值时,而子线程还没有执行完)
Runnable和Callable的转换:
工具类Executors可以实现Runnable对象和Callable对象之间的相互转换,把Runnable转成Callable:
(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule) )。
四、
ThreadPoolExecutor运行流程与生命周期
结合源码:
1、创建线程池:
使用构造函数创建,构造函数中参数见上文;
2、创建线程
2.1、规则:
默认情况下,创建了线程池后,线程池中的线程池数为0。
当有任务来之后(excute或sumbit提交线程),就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中:可见核心在addWorker
public void execute(Runnable command) {
if (command == null)
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
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);
}
2.2、原理
创建线程这里需要考虑并发的问题,即多个任务同时过来了,需要串行创建线程,否则,可能会导致超卖的情况(即创建的线程超过了最大线程数),具体是通过CAS乐观锁实现,代码解释如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();//获取当前线程池的控制状态。
int rs = runStateOf(c);//获取当前线程池的运行状态。
// Check if queue empty only if necessary.
//下面这个条件判断用于判断线程池是否处于关闭状态,并且任务队列不为空。如果线程池的状态大于等于SHUTDOWN,并且不满足线程池状态为SHUTDOWN、首个任务为null且任务队列为空的条件,则返回false。这个判断是为了确保在线程池关闭时,不再添加新的工作线程。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);//获取当前线程池中的工作线程数量。
//这个条件判断用于判断工作线程数量是否达到上限。如果工作线程数量大于等于CAPACITY(工作线程数量的上限)或者大于等于核心线程数(如果core为true)或最大线程数(如果core为false),则返回false。这个判断是为了确保工作线程数量不超过线程池的限制。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//尝试通过CAS(比较并交换)操作增加工作线程数量。如果成功增加工作线程数量,则跳出循环,继续执行后续逻辑。
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // 重新读取当前线程池的控制状态。
//再次判断线程池的运行状态是否发生了变化。如果运行状态发生了变化,则继续重试内部循环。这个判断是为了处理在CAS操作过程中,线程池的状态发生了变化的情况。
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
...创建线程的逻辑忽略...
}
3、线程执行
线程池的工作线程本质是死循环去处理任务,工作线程会不断的去阻塞队列中拉取任务:
线程池创建线程,调用了thread的start方法(addWorker方法中调用了start方法)
之后,该线程会走到线程池的runWorker方法(start启动实际运行run,run调用了runWorker)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
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);
}
}
4、线程销毁
4.1、规则:
当一个线程执行完或者遇到异常就会自动退出。但是核心线程默认会一直存活在线程池中,即使核心线程处理闲置状态;当然前面也提到了,可以设置allowCoreThreadTimeOut(true)使得核心线程在超过keepAliveTime之后销毁。
4.2、原理:
从runWorker中可以看出,线程想要一直存活,不退出程序就可以了即循环,销毁即跳出循环,关键点就在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;
//销毁线程需要满足这两个条件:1. (允许核心线程销毁 || 线程数大于核心线程数)&& 达到了销毁时间;2. 任务队列中没有任务了
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
// 这里返回了null,外层方法就跳出了while循环,从而结束该线程
return null;
continue;
}
try {
// 超时时间就是在这里设置的,如果允许超时销毁,那么就用poll进行拉取任务,超过了keepAliveTime就返回null。take是阻塞性等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
其中,阻塞队列的两个方法:
(1) poll:拉取任务(可能有等待时间),拉取不到(或超过等待时间)返回null;
(2)take:阻塞式的拉取任务,只要拉取不到,便一直阻塞;
具体走什么逻辑拉取任务取决于timed参数:
如果配置了allowCoreThreadTimeout参数,或者当前线程数大于核心线程数,则返回true,于是则使用poll方法获取参数。当拉取不到任务,将timeOut=true;在下一次自旋时,会使得当前工作线程数-1,并且返回null;不满足自旋条件,将completedAbruptly设置为true;
调用processWorkerExit方法去真正销毁自己,但此时,不一定真正被销毁; 如在销毁的流程中,发现阻塞队列中有任务需要被执行,但是该线程为最后一个线程时,会执行到分支2,再去开启while循环。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
//若允许销毁空闲的核心线程,则允许剩余的线程数为0
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//在销毁过程中,发现阻塞队列有任务,则允许剩余的线程数为1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//销毁过程中,工作线程数必须大于等于最小线程数,才允许销毁自己。
if (workerCountOf(c) >= min)
//分支1:结束流程,工作线程被销毁
return; // replacement not needed
}
//分支2:继续while循环去处理任务(该工作线程未被销毁)
addWorker(null, false);
}
}
从这里我们也能看到在处理任务的过程中,如果线程出现异常,则会将该线程从线程池中移除销毁,然后再新创建一个线程加入到线程池中,也就是说在任务发生异常的时候,会终结掉运行它的线程。
4.3、销毁普通线程
以上,可以看出,如果队列中没有任务时,小于核心数的线程(核心线程数不销毁的情况下)会一直阻塞在获取任务的方法,直到返回任务。(判断阻塞时并没有核心线程和非核心线程的概念,只要保证创建出来的线程销毁到符合预期数量就ok)。而且执行完后 会继续循环执行getTask的逻辑,不断的处理任务。所以线程池销毁并没有区分哪个是核心线程哪个是非核心线程,仅仅依据核心线程的数量。
4.3、销毁核心线程
从上面也可以看出,线程池销毁核心线程,依赖的API是queue的poll。当在keepAliveTime时间内拉取不到任务,则会中断工作线程的while循环,开始销毁任务。但是最终是否要销毁线程,还取决于阻塞队列中是否为空。
5、关闭线程池
有两种方法,只要调用了关闭方法中的任意一个,调用isShutDown方法返回true,当所有任务都关闭后才表示线程池已经关闭成功,调用isTerminate方法返回true。
5.1、shutdown()
将线程池状态设置为SHUTDOWN,不会立即关闭线程池,此时线程池不再接受新任务,等待缓存队列中的任务全部执行完成后终止线程。
5.2、shutdownNow()
将线程池状态设置为STOP,立即终止线程池,尝试中断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。