恰逢经济下行,鄙人工作、生活日趋艰难,原本美好的愿望,如今只能成为奢望。不知如何是好的我,只能精研近几年来因浮躁而荒废的知识。今天就想跟大家聊一个对我来讲看似熟悉实则陌生的工具——ThreadPoolExecutor。熟悉是因为在我负责的项目中它是一个出镜率最高演员;陌生是因为我对其内部工作原理一窍不通,而且它还是个高频考点。因此在这篇文章中,我想对其做个全面梳理,以便加深对它的理解,同时也希望通过这次梳理将那些累积已久的疑问全部解决掉。本次梳理的目标主要有以下几个:
- 弄清楚ThreadPoolExecutor的原理
- 彻底梳理清楚与ThreadPoolExecutor相关的知识
1 ThreadPoolExecutor简介
ThreadPoolExecutor是Java平台java.util.concurrent包中的一个核心组件,它是一种线程池实现类,用于管理和调度线程以高效地执行一组可并行或异步处理的任务。其继承结构图如下所示:
由图可知ThreadPoolExecutor是ExecutorService的实现类(注意:ExecutorService接口继承了顶级接口Executor,因此ThreadPoolExecutor是顶级接口Executor的实现类)。由此可以知道ThreadPoolExecutor 提供了一套灵活且强大的线程池框架,允许用户自定义线程池的各种行为和参数,以适应不同应用场景的需求。另外,使用线程池也有很多好处,比如:可以减少创建和销毁线程的开销;提高系统的响应性和吞吐量。下面就让一起看一下线程池的主要特点:
- 重用线程:线程可以在完成任务后被重用,而非每次任务都创建新的线程。(一直以来我都不清楚这个特征是怎么实现的,希望这篇文章能让我搞清楚这个问题)
- 控制并发级别:可以通过设置线程池的最大大小来控制并发任务的数量。
- 管理空闲线程:线程池可以管理空闲线程的存活时间,当没有任务执行时,线程会等待一定时间后终止。(关于这一点我也没有弄清楚是怎么回事)
下面再来看一下线程池ThreadPoolExecutor的核心参数,它的核心参数主要有下面几个,它们分别为(它们均定义在线程池的构造函数中):
- corePoolSize:线程池的基本大小,在任何时间都会维持这么多线程
- maximumPoolSize:线程池最大可以扩展到的线程数
- keepAliveTime:线程空闲时等待新任务的最长时间
- workQueue:用来存放等待执行任务的队列
- threadFactory:用于创建新线程的工厂
- handler:拒绝策略,当任务太多无法处理时采取的措施
下面就让我们一起看看线程池该如何使用。下面这个例子创建了一个线程池,其核心线程数为5,最大线程数为10,空闲线程的存活时间为60秒。如果提交的任务超过10个线程所能处理的范围,那么额外的任务将被存放在一个容量为5的阻塞队列中。如果队列也满了,那么再有任务提交时,将会根据指定的拒绝策略处理。在这个例子中,采用的是AbortPolicy,即抛出RejectedExecutionException异常:
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // timeUnit
new ArrayBlockingQueue<>(5), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.AbortPolicy() // handler
);
// 提交任务
for (int i = 0; i < 15; i++) {
final int taskId = i;
Runnable task = () -> System.out.println("Task ID: " + taskId + " is running by " + Thread.currentThread().getName());
executor.execute(task);
}
// 关闭线程池
executor.shutdown();
}
}
2拒绝策略
在前一小节中,我对ThreadPoolExecutor进行了简单的梳理,其中也指出了使用这个类时需要注意的一些核心数据,比如:核心线程数、最大线程数、线程等待时间、线程创建工厂、任务队列以及拒绝策略等。本小节我将梳理最后一个核心数据:拒绝策略。我一直在想这样一个问题:程序作为没有生命意识的实体,它真的会像人那样拒绝吗?被它拒绝的任务最后又去了哪里呢?“什么,居然有人问这种问题,他不会是个傻子吧!拒绝就是拒绝,拒绝就是不执行,拒绝就是不浪费计算机资源,这有那么难理解吗?”是的,现在的我肯定不会再问这种问题,但当初它确实让我苦恼了很久。站在线程池的角度,用生活中常见的例子再来理解一遍,被那个问题困扰很久的我仿佛痴子一般:作为一款只能处理十字纹的十字螺丝刀,你用它处理一字螺丝可行吗?作为需求方的你会怎么做?换螺丝刀。对!线程池就如这十字螺丝刀一样,它只知道自己要处理什么,该处理什么,没有义务为这个自己无法处理的任务提供解决方案,这是你需求方的事。是不是有点不近人情?但事实就是这样!螺丝刀不会讲话,只能你这个使用工具的人想其他办法。因此换到线程池中也是一样的,线程池只负责调度能力范围内的任务,对于那些自己无法处理的任务通知线程池使用方即可,由其按照自己的业务需求指定可靠的处理方案。因此现在看来,当时困扰我的问题其实是自己没有弄清主次造成的。下面就让我们一起看一下Java提供的几种内置的拒绝策略,每种策略都有其适用场景:
- AbortPolicy:默认策略,简单地抛出 RejectedExecutionException 异常。这会导致调用者意识到任务被拒绝,并可能采取相应的补救措施
- CallerRunsPolicy:调用者的线程直接运行任务。这意味着调用 execute 方法的线程会执行任务,而不是将其放入队列中等待执行。这种策略不会丢弃任务,但是可能会降低程序的整体性能
- DiscardOldestPolicy:默默地丢弃任务,不抛出任何异常。这种策略适用于对任务丢失不敏感的情况
- DiscardPolicy:丢弃队列中最旧的任务,然后重试执行当前任务。这种策略有助于确保较新的任务能够得到执行
以上4种拒绝策略都实现了RejectedExecutionHandler接口,这个接口定义了线程池如何处理那些无法被接收的任务。具体可以参见下面这幅类结构图:
当线程池中的所有工作线程都在忙于处理任务,并且任务队列也已满时,如果此时仍有新的任务提交给线程池,就需要通过拒绝策略来决定如何处理这些任务。除了上述四种内置策略外,还可以自定义拒绝策略。譬如,可以通过记录日志、发送通知等方式来处理被拒绝的任务。下面看一个自定义拒绝策略示例,该策略记录被拒绝的任务信息,并尝试重新提交任务:
class CustomRejectHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
// 尝试重新提交任务
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
}
} else {
// 如果线程池已经关闭,则记录日志,或者将被拒绝的任务持久化到数据库中
System.err.println("Task " + r.toString() + " rejected");
}
}
}
3 线程创建工厂
刚接触到这个属性时,我是非常困惑的:为什么这里会有这样一个属性,调用线程池对象时传递的Runnable实现类不就是一个线程吗?直接执行这个线程不就把要执行的任务给执行了吗?从理论上讲,任务执行完后这个线程不是会被系统回收吗?那后续提交的任务怎么可能会有线程复用的情况发生呢?如果是这个思路,那这里为什么会有线程工厂这个属性呢?下面就让我们一步步的分析一下:
首先在Java中,ThreadFactory是一个接口,其主要作用是创建新的线程。ThreadFactory允许用户自定义线程创建的过程,这对于控制线程的属性(如优先级、线程组、名称等)等是非常有用的。java.util.concurrent包提供了ThreadFactory接口(注意:该接口中只有一个newThread(Runnable)方法),以及一些实现类。具体看下面可以参见下面这幅类结构图:
上图中的类均来自java.util.concurrent包,其中DefaultThreadFactory和PrivilegedThreadFactory两个类均为Executors类中的私有静态内部类。而DaemonThreadFactory类位于CompletableFuture类中。下面让我们回过头看一下开头提出的那个问题:为什么会有线程工厂这个属性?其实这个问题的答案在本小节第二段中:为了创建线程,所以这里要有一个ThreadFactory属性。换句话说,这个属性的主要作用是:通过可控的方式创建一个符合对应场景的线程对象。下面就来看一下DefaultThreadFactory类的定义:
private 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;
}
}
从代码可以看出DefaultThreadFactory类通过new方式创建一个线程对象,并将其返回给调用者。这个类的调用入口在Worker类的构造方法中,调用该方法时会将当前Worker对象传递进去,具体代码为:getThreadFactory().newThread(this)。
4 ThreadPoolExecutor究竟怎么实现的?
前面介绍了很多线程池使用上的知识,譬如相关的属性,特殊属性的自定义思路等。不过对于这个保存线程的容器,其究竟是怎么实现的,我个人并不了解。这节想就这个问题深入研究一番。首先作为存储线程的容器,其先要保证自身稳定,而后才能存放线程,这就像破了洞的水盆无法盛水一样;接着线程池要提供简单易用的API接口,以便于使用者快速使用。那java是如何实现这个盛放线程的容器的?主要分为以下几个方面:
- 容器。ThreadPoolExecutor本身就是一个容器,对于这个容器,其有自己的状态等属性。其中线程池的状态通过int类型的高3位来表示(理论上线程池共有7中状态),容器中线程的数量通过int类型的低29位来表示(理论上线程池可以存放268435455个线程,不知道这么理解是否正确)。因此ThreadPoolExecutor类提供了这样几个方法:runStateOf(int)、workerCountOf(int)、ctlOf(int, int)、runStateLessThan(int, int)、runStateAtLeast(int, int)、isRunning(int)、compareAndIncrementWorkerCount(int) 、decrementWorkerCount()、compareAndDecrementWorkerCount(int)等。这些方法都与判断线程池状态及数量等的操作有关。说到这里不得不提一下线程池的状态,java定义的线程池状态有:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。不知道这段理解有没有问题?
- 线程管理。为了更好的管理池中的线程,ThreadPoolExecutor定义了一些相关的属性,譬如:BlockingQueue<Runnable>类型的工作队列-workerQueue(主要用于存放使用方提交的任务)、int类型的最大线程数-maximumPoolSize、int类型的核心线程数-corePoolSize、long类型的线程超时时间-keepAliveTime、ThreadFactory类型的线程工厂-threadFactory、RejectedExecutionHandler类型的拒绝策略、HashSet<Worker>类型的工作线程。其他还有ReentrantLock类型的锁mainLock、long类型的largestPoolSize、long类型的completedTaskCount。(注意:ThreadPoolExecutor默认的拒绝策略是AbortPolicy)
一般使用线程池的方法非常简单,就是用线程池对象调用execute(new Runnable(){//实现run()方法})方法或submit(new Runnable(){//实现run()方法})。下面我想就execute()方法的执行流程谈谈自己的理解:
public void execute(Runnable command) {
// 检查传递进来的任务类是否为 null,如果为 null,则抛出空指针异常
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.
*/
// 获取正在运行的线程数,如果正在运行的线程少于核心线程数,则启动一个新线程处理这个任务
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);
}
//
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && 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;
}
这段代码看起来很长,但这里要梳理的核心逻辑不是特别多。这段代码的具体的执行过程如下所示:
- 调用execute(Runnable command)方法,提交一个任务
- execute()方法会首先判断当前任务是否为null。a) 如果是,则直接抛出异常;b) 如果不是则拿到ThreadPoolExecutor中的计数器ctl的值,调用workerCountOf(ctl)值获取当前正在运行的工作线程的数量,比较这个值与核心线程数之间的关系,如果小于核心线程数,则调用addWorker()方法创建Worker对象,将其添加到ThreadPoolExecutor类的workers属性中,然后启动线程执行第一步提交的command。c) 如果正在运行的线程数大于核心线程数,则将提交的command添加到ThreadPoolExecutor类的workQueue队列中,如果添加成功则会继续检查看是否需要启动线程执行或者拒绝这个命令。d) 如果正在运行的线程数大于核心线程数,并且无法像任务队列中添加任务,那么直接调用addWorker()方法来创建Worker对象,并将其添加到ThreadPoolExecutor类的workers属性中,然后启动线程执行第一步提交的command。e) 如果这次调用addWorker()方法得到了false,则表示当前任务需要按照拒绝策略进行拒绝。(注意:d、e这两步的主要作用是判断当前运行的线程数与最大线程数之间的关系,如果小于就创建线程,如果大于就执行拒绝策略)
5 Worker是什么?
Worker是ThreadPoolExecutor类中的一个私有内部类。它被final字段修饰,说明这个类不能被其他类继承。它实现Runnable接口,同时又继承AbstractQueuedSynchronizer类,因此Worker是一个线程类,可以被独立调用。在我的印象中所谓的线程就是一个具有独立业务处理逻辑的类,比如下面这个类:
class Task implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000 * 60 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("aaaa");
}
}
}
从代码不难看出,这个类非常简单:它被调度后会休眠1小时,然后在控制台打印一串字符串aaaa。这有什么意义呢?我虽然无法明确其实际意义,但也不能简单地说它毫无意义。就像我们不能武断的认为一个不聪明的人毫无用处一样。既然如此,那就是说在工业级软件中存在这样的类?这种说法我觉得有点草率。还是一起来看看Worker吧!个人理解这个类就是一个工人,不过这个工人并非通常意义上的工人。这个工人大脑比较简单,只能识别一些简单的指令,比如吃饭、工作、休息等。为了更好的被别人操控,其甚至不顾家人反对再次自我阉割,完全成为一个毫无意识得工具人(别人说做什么,它就无条件做什么。说白了Worker就是一个打工人,是一个执行Runnable接口实现类的工具)。Worker类中有这样几个属性:
- long类型的serialVersionUID属性。这个属性是所有实现java.io.Serializable接口的类中必须存在的一个属性。
- Thread类型的thread属性。这个属性是用于调度Task线程的。这个Thread对象一般通过创建ThreadPoolExecutor类时指定的ThreadFactory对象创建。
- Runnable的firstTask属性。这个属性的值一般是调度ThreadPoolExecutor对象时指定的Runnable接口实现类,比如上面那个具有打印功能的Task对象。
- valatile long类型的completedTasks属性。这个属性用于记录当前Worker线程执行的任务的数量。
Worker类提供的方法有这样几个:
- public void run():这个方法来自于Runnable接口。其核心代码为runWorker(this),这是ThreadPoolExecutor类中的一个方法。该方法执行逻辑为:1)取出当前的线程对象(Thread wt=Thread.currentThread(););2)从Worker对象中取出任务线程对象(Runnable task=w.firstTask),同时将Worker对象的firstTask属性设置为null(w.firstTask = null;);3)调用Worker对象上的unlock()方法(w.unlock());4)通过while循环的方式判断取出的task对象是否null,如果不为null,则进入while循环体,否则执行completedAbruptly=false,并执行finally中的代码(调用processWorkerExit(Worker对象, completedAbruptly)方法)【关于while循环体的执行逻辑的梳理:调用Worker对象上的lock()方法;判断线程池的状态,如果线程池被终止,则调用当前线程对象wt上的interrupt()方法;否则执行beforeExecute(wt, task)方法,执行task上的run()方法,接着执行afterExecute(task, null)方法。如果上面执行过程抛出异常,则执行catch块的afterExecute(task, null),否则执行finally块中的代码(task=null;w.completedTasks++;w.unlock();)】。这里有一点需要注意:通过runWorker()方法的执行逻辑可以看出,提交给线程池的任务虽然实现了Runnable接口,但其并没有被线程池当作一个线程对象使用,也就是说并未将该对象交由Thread对象管理并启动新线程处理,而是直接调用其中的run()方法。直白讲线程池将实现Runnable接口的任务类当作一个普通的java对象使用;beforeExecute(Thread t, Runnable r)和afterExecute(Runnable r, Throwable t)两个方法被定义在ThreadPoolExecutor类中。
- protected boolean isHeldExclusively():该方法源于AbstractQueuedSynchronizer接口,表示当前锁是否处于占用状态
- protected boolean tryAcquire(int unused) :这个方法来自于AbstractQueuedSynchronizer接口,表示尝试加锁(当状态为0的时候获取锁,CAS操作成功,则state状态为1)
- protected boolean tryRelease(int unused) :这个方法来自于AbstractQueuedSynchronizer接口,用于释放锁(释放锁,将同步状态置为0)
- public void lock():这个是Worker对外提供的加锁方法。
- public boolean tryLock():这个是Worker对外提供的尝试加锁方法
- public void unlock():这个是Worker对外提供的解加锁方法
- public boolean isLocked():这个是Worker对外提供的查看当前锁是否锁定的方法
- void interruptIfStarted():这个是Worker对外提供的一个线程中断方法。
6 线程池的调度细节
在第5、第6小节中我们梳理了线程池任务提交的流程以及Worker类的作用,这一小节我将在这两小节的基础上梳理一下线程池调度任务的具体过程。首先在这里啰嗦几句:前面梳理任务提交流程时,我们看到在addWorker(Runnable, true)中有这样一段逻辑,具体见下图:
通过这段代码可以看出我们交给线程池的任务,最终由Worker对象托管(这个从new Worker(Runnable)这段代码即可看出),这个类在第6小节中梳理过,其有一个Runnable类型的属性firstTask,由一个Thread类型的属性thread,其中firstTask表示的是我们提交给线程池的任务,thread表示的是调度线程的Thread对象。在创建完Worker对象后,会从Worker对象中取出其持有的Thread对象,然后执行t.start()方法。注意:调用start()方法后,Worker所代表的工作线程就开始工作了(为什么会这样?)。下面让我们看一下Worker类的的构造方法和run()方法的源码:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
// runWorker() 方法位于 ThreadPoolExecutor 类中
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);
try {
// ***************************************
task.run();
// ***************************************
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
从这段代码中的构造方法中不难发现,Worker对象持有的Thread对象最终持有的Runnable对象就是当前的Worker对象,所以当执行前面说的t.start()时,实际调度的就是当前的Worker线程。那这个Worker线程究竟做了什么?这还得看Worker线程的run()方法,从这个方法的源码不难知道,其调用了ThreadPoolExecutor类中的runWorker(Runnable)方法。这个方法的做的事情也很简单:从Worker对象中拿到Runnable类型的task,然后调用该任务上的run()方法(具体参见上述源码中星号标识的代码)。通过梳理,我们可以发现提交给线程池的任务,虽然实现了Runnable接口,但最终只是被线程池当作一个普通的java对象使用,而非线程对象。
7 总结
通过这篇文章,我对ThreadPoolExecutor的认识变得更加清晰,更加全面和系统化。这与之前那种觉得懂却又说不出个所以然的状态相比,显然进步了不少。这也是我在平台发布这篇文章的目的。通过这篇文章我主要学习到了以下几点:
- 线程池的基本知识。ThreadPoolExecutor是Java平台java.util.concurrent包中的一个核心组件,它是一种线程池实现类,用于管理和调度线程以高效地执行一组可并行或异步处理的任务。
- 线程池的核心参数有:核心线程数、最大线程数、线程工厂、拒绝策略、线程超时时间、任务队列等
- 线程池的任务提交流程为:先判断当前线程池中是否存在正在运行的工作线程,并判断其与核心线程数的关系。如果小于核心线程数,就创建工作线程并执行当前任务;如果大于核心线程数,则将当前提交的任务放置到任务队列中等待有空闲线程时进行调度;如果任务队列中不存在足够的空间,则判断线程池中活跃的线程数与最大线程数的关系,如果小于最大线程数,则创建新的工作线程执行当前被提交的任务;如果大于最大线程数,则启动拒绝策略。java线程池主要提供了四种拒绝策略:AbortPolicy(默认,直接抛出RejectedExecutionException);CallerRunsPolicy(调用者的线程直接运行任务。这意味着调用 execute 方法的线程会执行任务,而不是将其放入队列中等待执行。这种策略不会丢弃任务,但是可能会降低程序的整体性能);DiscardOldestPolicy(默默地丢弃任务,不抛出任何异常。这种策略适用于对任务丢失不敏感的情况);DiscardPolicy(丢弃队列中最旧的任务,然后重试执行当前任务。这种策略有助于确保较新的任务能够得到执行)
- Worker是ThreadPoolExecutor类中的一个私有内部类。其本质就是一个打工仔,是一个执行实现Runnable接口的任务的工具
- 被线程池调度的任务,虽然实现了Runnable接口,但其仅仅被作为普通java对象用,而非线程对象使用