前言
Java线程池是提高应用性能的关键组件。线程池通过预先创建并管理一组线程,可以显著减少因频繁创建和销毁线程而产生的资源消耗。本文将探讨Java线程池的基本概念、创建方法以及最佳实践。
需要注意的是,【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
一般情况下,推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。
1.线程池介绍
线程池(ThreadPool)是一种资源管理策略,它通过复用线程来降低资源消耗、提高响应速度,并增强线程管理的可操作性。线程池预分配一定数量的线程,当任务到来时,线程池会分配现有线程去执行任务,而不是每次都创建新的线程。
线程池的优点如下: 资源节约:通过复用线程,减少了线程创建和销毁的开销。 性能提升:任务可以快速启动,因为线程已经存在。 管理增强:线程池提供了更多的控制,如线程数量、任务队列等。
2.线程池创建的方式
Java中创建线程池主要有两大类方法:
使用ThreadPoolExecutor直接创建:提供了最大的灵活性和控制力。 使用Executors工厂方法创建:提供了多种快捷方式来创建常见的线程池类型。
2.1 ThreadPoolExecutor的详细配置
ThreadPoolExecutor是最灵活的线程池创建方式,允许开发者自定义线程池的各项参数:
核心线程数:线程池中始终存活的线程数。 最大线程数:线程池中允许的最大线程数。 存活时间:非核心线程在没有任务执行时的存活时间。 时间单位:与存活时间配合使用的时间单位。 工作队列:存储等待执行任务的阻塞队列。 线程工厂:用于创建新线程的工厂。 拒绝策略:当任务太多无法处理时的策略。
2.2 Executors提供的快捷创建方法
Executors类提供了一些快捷方法来创建特定类型的线程池:
FixedThreadPool:固定大小的线程池。 CachedThreadPool:可缓存的线程池,会根据需要创建新线程。 SingleThreadExecutor:单个线程的线程池,保证任务顺序执行。 ScheduledThreadPool:可以执行定时任务的线程池。 WorkStealingPool:JDK 1.8新增,任务被多个线程池线程抢占执行。
3.线程池使用场景
FixedThreadPool适用于需要固定数量线程执行任务的场景。 CachedThreadPool适合处理大量短期异步任务。 SingleThreadExecutor保证任务按照提交的顺序执行。 ScheduledThreadPool适合需要定时或周期性执行任务的场景。
4.最佳代码实践
4.1 ThreadPoolExecutor
代码语言:javascript复制public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
// 执行任务
for (int i = 0; i < 10; i ) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index " 被执行,线程名:" Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
可以设置7个参数
代码语言:javascript复制public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {}
- corePoolSize:核心线程数,线程池中始终存活的线程数。
- maximumPoolSize:最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
- keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
- unit:单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间。
TimeUnit.DAYS:天 TimeUnit.HOURS:小时 TimeUnit.MINUTES:分 TimeUnit.SECONDS:秒 TimeUnit.MILLISECONDS:毫秒 TimeUnit.MICROSECONDS:微妙 TimeUnit.NANOSECONDS:纳秒
- workQueue:一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全。它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。 SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。 PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。 DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。 LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。 较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关
- threadFactory:线程工厂,主要用来创建线程。
- handler:拒绝策略,拒绝处理任务时的策略,系统提供了 4 种。
AbortPolicy:拒绝并抛出异常。(默认策略) CallerRunsPolicy:使用当前调用的线程来执行此任务。 DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。 DiscardPolicy:忽略并抛弃当前任务。
ThreadPoolExecutor 关键节点的执行流程如下:
1、当线程数小于核心线程数时,创建线程。 2、当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。 3、当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。
4.2 FixedThreadPool
FixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
一般用于Web 服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
代码语言:javascript复制public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
- corePoolSize 与 maximumPoolSize 相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势。
- keepAliveTime = 0 该参数默认对核心线程无效,而 FixedThreadPool 全部为核心线程。
- workQueue 为 LinkedBlockingQueue(无界阻塞队列),队列最大值为 Integer.MAX_VALUE。如果任务提交速度持续大于任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势
- FixedThreadPool 的任务执行是无序的。
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
System.out.println("主线程启动");
// 1.创建1个有2个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("任务被执行,线程:" Thread.currentThread().getName());
}
};
// 2.线程池执行任务(添加4个任务,每次执行2个任务,得执行两次)
threadPool.submit(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
System.out.println("主线程结束");
}
}
线程池通过复用一组固定数量的线程来执行多个任务,这些线程在一个共享的无界队列上操作。在任一时刻,至多有 nThreads 个线程在积极地处理任务。如果所有线程都忙碌且此时有新任务提交,那么这些新任务将被放入队列中排队,直到有线程空闲出来。
该线程池能够同时处理两个任务,因为有两个活跃的线程。如果这两名线程都在执行任务,那么新提交的两个任务将进入等待队列,直到这两个线程中的任何一个完成其当前任务。
在Java的线程池中,submit() 和 execute() 是两种不同的方法,它们都用于向线程池提交任务。submit() 方法允许你提交一个任务,并返回一个 Future 对象,这个对象可以用来查询任务状态、取消任务或获取任务执行结果。相比之下,execute() 方法用于提交一个任务以供执行,但它不返回任何表示任务的 Future 对象。
4.3 CachedThreadPool
代码语言:javascript复制import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CachedThreadPoolExample {
public static void main(String[] args) {
// 创建 CachedThreadPool 线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 提交任务到线程池
for (int i = 0; i < 10; i ) {
final int taskNumber = i;
threadPool.execute(() -> {
System.out.println("执行任务 " taskNumber " 由线程 " Thread.currentThread().getName() " 处理");
try {
TimeUnit.SECONDS.sleep(1); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池,不再接受新任务
threadPool.shutdown();
}
}
- CachedThreadPool 是通过Executors.newCachedThreadPool() 方法创建的,它是一种可扩展的线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE。
- 该线程池适合用于执行大量短期异步任务。它在需要时会动态创建新线程,如果线程空闲时间超过 60 秒,则会被终止并从线程池中移除。
- CachedThreadPool 使用的是一个同步队列 SynchronousQueue 作为工作队列,这个队列没有容量,即它不会存储提交的任务,而是直接将任务交给线程执行。
- 在示例代码中,我们创建了一个 CachedThreadPool 并提交了 10 个任务。每个任务简单地打印出它正在被哪个线程执行,并模拟执行时间。
- 最后,我们调用 shutdown() 方法来关闭线程池,使其不再接受新任务。注意,这不会立即停止所有正在执行的任务,而是等待它们完成后线程池才会完全关闭。
使用 CachedThreadPool 时需要注意,由于其最大线程数可以非常大,如果任务提交得非常快,可能会导致创建大量线程,从而耗尽系统资源。因此,应当谨慎使用,并确保任务执行不会过快,或者考虑设置适当的线程池参数。
4.4 SingleThreadExecutor
代码语言:javascript复制import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
// 创建 SingleThreadExecutor 线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 提交多个任务到单线程线程池
for (int i = 1; i <= 5; i ) {
final int taskNumber = i;
threadPool.execute(() -> {
System.out.println("执行任务 " taskNumber " 由线程 " Thread.currentThread().getName() " 处理");
try {
TimeUnit.SECONDS.sleep(2); // 模拟耗时任务
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断状态
System.out.println("任务 " taskNumber " 被中断");
}
});
}
// 优雅关闭线程池,等待所有任务执行完毕
threadPool.shutdown();
try {
// 等待线程池关闭,或者超时
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时后强制关闭线程池
threadPool.shutdownNow();
}
} catch (InterruptedException e) {
threadPool.shutdownNow(); // 捕获中断异常时立即关闭线程池
Thread.currentThread().interrupt(); // 重新设置中断状态
}
}
}
SingleThreadExecutor 是通过 Executors.newSingleThreadExecutor() 方法创建的,它确保所有的任务都在同一个线程中按顺序执行。
这种类型的线程池内部其实只有一个线程在工作,也就是单线程环境,它保证了任务的执行顺序,即先提交的任务先执行。
在示例代码中,我们创建了一个 SingleThreadExecutor 并提交了 5 个任务。每个任务简单地打印出它正在被哪个线程执行,并模拟执行时间。
我们使用了 shutdown() 方法来开始关闭线程池的过程,这将阻止线程池接受新任务,但会等待已提交的任务执行完毕。
awaitTermination 方法用来等待线程池中的所有任务执行完成,或者直到超时。如果超时,则会调用 shutdownNow() 方法尝试立即停止所有正在执行的任务。 SingleThreadExecutor 适用于任务不需要并发执行,并且希望按照特定顺序执行的场景。
4.5 ScheduledThread
代码语言:javascript复制public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
System.out.println("添加任务,时间:" new Date());
threadPool.schedule(() -> {
System.out.println("任务被执行,时间:" new Date());
}, 2, TimeUnit.SECONDS);
}
}
4.6 SingleThreadScheduledExecutor
代码语言:javascript复制import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SingleThreadScheduledExecutorExample {
public static void main(String[] args) {
// 创建 SingleThreadScheduledExecutor 线程池
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// 提交一个任务,延迟2秒后执行
scheduledExecutorService.schedule(() -> {
System.out.println("任务在 " TimeUnit.SECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS) " 秒后执行");
}, 2, TimeUnit.SECONDS);
// 重复执行的任务,每隔2秒执行一次,初始延迟2秒
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("周期性任务执行,时间:" new java.util.Date());
}, 2, 2, TimeUnit.SECONDS);
// 线程池不会立即关闭,因为任务还在执行
// 可以安排关闭线程池的逻辑,例如使用 ScheduledExecutorService 的 shutdown 方法
}
}
SingleThreadScheduledExecutor 是通过 Executors.newSingleThreadScheduledExecutor() 方法创建的,它是一个单线程执行定时任务的线程池。
与SingleThreadExecutor不同,SingleThreadScheduledExecutor支持定时任务和周期性任务的执行。
在示例代码中,我们首先使用schedule方法提交了一个延迟2秒后执行的单次任务。
然后,我们使用scheduleAtFixedRate方法提交了一个周期性任务,该任务每隔2秒执行一次,并且有一个初始延迟2秒。
SingleThreadScheduledExecutor 保证所有任务都在同一个线程中顺序执行,这对于需要保证任务执行顺序的场景非常有用。
由于SingleThreadScheduledExecutor是为定时任务设计的,所以它不会像shutdown方法那样立即关闭线程池。如果需要关闭线程池,应该在所有任务执行完毕后调用shutdown方法,并妥善处理关闭逻辑。
此类型的线程池适用于执行定时任务和周期性任务,如定期的数据备份、定时检查等场景。
4.7 NewWorkStealingPool
NewWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定),任务的执行顺序是不确定的,注意此方法只有在 JDK 1.8 版本中才能使用。
代码语言:javascript复制public class NewWorkStealingPoolTest {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i ) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index " 被执行,线程名:" Thread.currentThread().getName());
});
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}
}
}
5.任务队列
5.1 直接提交队列(SynchronousQueue)
直接提交队列不存储任务,每个提交的任务必须立即由线程池中的某个线程接收并开始执行。
代码语言:javascript复制import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
public class DirectSubmissionQueueExample {
public static void main(String[] args) {
// 使用直接提交队列
SynchronousQueue<Runnable> queue = new SynchronousQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, queue);
// 提交任务
executor.execute(() -> {
System.out.println("任务被执行,线程:" Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 关闭线程池
executor.shutdown();
}
}
在这个示例中,我们创建了一个核心线程数和最大线程数都为1的ThreadPoolExecutor,使用了SynchronousQueue作为任务队列。任务一提交就会尝试执行,因为没有存储机制,所以任务不会被缓存。
5.2 有界任务队列(ArrayBlockingQueue)
有界任务队列可以存储有限数量的任务。当队列满了之后,根据拒绝策略处理新提交的任务。
代码语言:javascript复制import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class BoundedQueueExample {
public static void main(String[] args) {
// 创建有界任务队列
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, TimeUnit.MILLISECONDS, queue);
// 提交任务
for (int i = 0; i < 3; i ) {
executor.execute(() -> {
System.out.println("任务 " (i 1) " 被执行,线程:" Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
在这个示例中,我们创建了一个有界队列ArrayBlockingQueue,容量为2。这意味着它最多可以存储两个任务。当提交的任务超过这个数量时,根据设置的拒绝策略处理。
5.3 无界任务队列(LinkedBlockingQueue)
无界任务队列可以存储无限数量的任务,直到内存限制。
代码语言:javascript复制import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
public class UnboundedQueueExample {
public static void main(String[] args) {
// 创建无界任务队列
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, TimeUnit.MILLISECONDS, queue);
// 提交任务
for (int i = 0; i < 10; i ) {
executor.execute(() -> {
System.out.println("任务 " (i 1) " 被执行,线程:" Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
在这个示例中,我们创建了一个无界队列LinkedBlockingQueue。这意味着它可以存储任意数量的任务,直到系统内存耗尽。
5.4 优先任务队列(PriorityBlockingQueue)
优先任务队列可以根据任务的优先级来执行任务。
代码语言:javascript复制import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建优先任务队列
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, TimeUnit.MILLISECONDS, queue);
// 提交任务,可以定义任务优先级
executor.execute(new PrioritizedTask(5, "任务5"));
executor.execute(new PrioritizedTask(1, "任务1"));
executor.execute(new PrioritizedTask(10, "任务10"));
// 关闭线程池
executor.shutdown();
}
static class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> {
private int priority;
private String taskName;
public PrioritizedTask(int priority, String taskName) {
this.priority = priority;
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(taskName " 被执行,线程:" Thread.currentThread().getName());
}
@Override
public int compareTo(PrioritizedTask other) {
return Integer.compare(this.priority, other.priority); // 升序排序
}
}
}
在这个示例中,我们创建了一个PriorityBlockingQueue,它根据任务的优先级来排序任务。我们定义了一个PrioritizedTask类,实现了Runnable和Comparable接口,以支持优先级排序。任务将根据它们的优先级被执行。
6.线程拒绝策略
我们来演示一下 ThreadPoolExecutor 的拒绝策略的触发,我们使用 DiscardPolicy 的拒绝策略,它会忽略并抛弃当前任务的策略,实现代码如下:
代码语言:javascript复制public class ThreadPoolStrategyTest {
public static void main(String[] args) {
// 线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
// 任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("当前任务被执行,执行时间:" new Date()
" 执行线程:" Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
};
// 开启4个任务
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}
除了 Java 自身提供的 4 种拒绝策略之外,我们也可以自定义拒绝策略,示例代码如下:
代码语言:javascript复制public class MyThreadPoolStrategyTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("当前任务被执行,执行时间:" new Date()
" 执行线程:" Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 执行自定义拒绝策略的相关操作
System.out.println("我是自定义拒绝策略~");
}
});
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}
以下是自定义线程池,使用了有界队列,自定义 ThreadFactory 和拒绝策略的demo:
代码语言:javascript复制public class MyThreadPoolTest {
public static void main(String[] args) throws Exception {
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
NameThreadFactory threadFactory = new NameThreadFactory();
RejectedExecutionHandler handler = new MyIgnorePolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS,
workQueue, threadFactory, handler);
// 预启动所有核心线程
executor.prestartAllCoreThreads();
for (int i = 1; i <= 10; i ) {
MyTask task = new MyTask(String.valueOf(i));
executor.execute(task);
}
//阻塞主线程
System.in.read();
}
static class NameThreadFactory implements ThreadFactory {
private final AtomicInteger mThreadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "my-thread-" mThreadNum.getAndIncrement());
System.out.println(t.getName() " has been created");
return t;
}
}
static class MyIgnorePolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
doLog(r, executor);
}
private void doLog(Runnable r, ThreadPoolExecutor e) {
// 可做日志记录等
System.err.println( r.toString() " rejected");
System.out.println("completedTaskCount: " e.getCompletedTaskCount());
}
}
static class MyTask implements Runnable {
private String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println(this.toString() " is running!");
// 让任务执行慢点
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
@Override
public String toString() {
return "MyTask [name=" name "]";
}
}
}