引言
中文翻译是ExecutorService使用指南,整体看下来入门但是能通过本文快速概览和学习Executors的使用。
ExecutorService是JDK的一个API,它简化了异步模式下的任务运行。一般来说,ExecutorService会自动提供一个线程池和一个用于向其分配任务的API。
1. Java ExecutorService 指南
A Guide to the Java ExecutorService | Baeldung
2. Instantiating ExecutorService
2.1. Factory Methods of the Executors Class
最简单的构建线程池的方法是使用 Executors 工厂类默认的工厂方法。举个例子,比如下面的方法构建一个大小为10的线程池:
ExecutorService executor = Executors.newFixedThreadPool(10);
更多关于这个工厂类的用法可以阅读:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executors.html
2.2. Directly Create an ExecutorService
ExecutorService 是典型的面向接口设计,对应的所有实现类都可以作为返回结果,从jaav.util.concurrent
的包构建ExecutorService的实现(比如Executors工厂方法),当然也可以像下面这样直接实现:
/**
* 不推荐使用除开
*/
@Deprecated
private static void newExecutorService() {
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
但是这种写法实际上和Executors的写法没有相差多少,本着不重复造轮子以及避免犯错的理念,这里不建议开发者自行构建ExecutorService实现,除非对并发类设计非常了解。
3. Assigning Tasks to the ExecutorService
ExecutorService可以执行Runnable和Callable任务。下面的案例当中,为了保持简单将使用两个原始的任务。请注意在这里使用lambda表达式而不是匿名的内部类实现。
public static void main(String[] args) throws ExecutionException, InterruptedException {
assignTaskToExecutor();
}
/**
* 不推荐使用除开
*/
@Deprecated
private static ExecutorService newExecutorService() {
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
return executorService;
}
public static void assignTaskToExecutor(){
// 传统 Runnable 任务
Runnable runnableTask = () -> {
try {
System.out.println(Thread.currentThread().getName()+" runnableTask");
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
ExecutorService executorService = newExecutorService();
executorService.execute(runnableTask);
// Callable 任务
Callable callableTask = () -> {
System.out.println(Thread.currentThread().getName()+" callableTask");
TimeUnit.MILLISECONDS.sleep(300);
return "Task's execution "+Thread.currentThread().getName();
};
List> callableTasks = new ArrayList();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);
}
我们可以使用这几个方法将任务分配给ExecutorService,包括execute(),这是从Executor接口继承的,还有submit()、invokeAny()和 invokeAll()。
execute() 是没有返回结果的,void
的返回值导致没有任何获取返回结果的途径。
executorService.execute(runnableTask);
Future> submit = executorService.submit(runnableTask);
// runable返回结果为null
Object o = submit.get();
System.out.println(o);
/**
pool-1-thread-1 runnableTask
pool-1-thread-1 runnableTask
Runnable的返回结果为null
null
*/
submit() 将一个Callable或Runnable任务提交给一个ExecutorService,并返回一个Future类型的结果。
for (Callable task : callableTasks) {
Future> callResult = executorService.submit(task);
Object callRes = callResult.get();
System.err.println(callRes);
}/**
pool-1-thread-1 callableTask Task's execution pool-1-thread-1 callableTask Task's execution pool-1-thread-1 callableTask Task's execution */
invokeAny() 将一个任务集合分配给一个ExecutorService,使每个任务运行,并返回(任意)一个任务成功执行的结果(如果有一个成功的执行)。
System.out.println("n");
String result = executorService.invokeAny(callableTasks);
System.err.println(result);
/**
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
Task's executionpool-1-thread-1
*/
invokeAll() 每次运行将一个任务集合分配给一个ExecutorService,并以Future类型的对象列表的形式,返回所有任务的执行结果。
List> futures = executorService.invokeAll(callableTasks);
for (Future future : futures) {
System.out.println("收到回调" + future.get());
}/**
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
收到回调Task's execution pool-1-thread-1
收到回调Task's execution pool-1-thread-1
收到回调Task's execution pool-1-thread-1
*/
在进一步讨论之前,我们还需要讨论另外两个话题:关闭ExecutorService和处理Future返回类型。
4. Shutting Down an ExecutorService
通常情况下 ExecutorService 的线程池是不会自动关闭的,如果当前线程池没有任务,就会一直等待直到有新任务进入。
在某些情况下,这是很有帮助的,例如当一个应用程序需要处理不定期出现的任务,或者在编译时不知道任务数量。
但是另一方面又会因为这种看似“无用”的等待,占用JVM的线程而导致一个应用程序一直处于运行状态。
Idea中会发现程序并不会结束。
为了正确关闭ExecutorService,我们要使用 shutdown() 和 shutdownNow() 的 API。
问题来了,两者的区别是什么?
shutdown():方法并不会导致ExecutorService的立即销毁。它将使ExecutorService停止接受新的任务,并在所有运行的线程完成其当前工作后关闭(延后处理)。也就是类似“中断”的方式关闭线程池。
// 关闭线程池
executorService.shutdown();
shutdownNow():shutdownNow()方法试图立即销毁ExecutorService,但它不能保证所有正在运行的线程都能同时停止。
作为“补偿”,shutdownNow() 方法提供了返回值,所有没有执行完成的线程任何组装为一个List返回,有开发者决定如何处理这些没有完成的任务。
这里需要注意,如果线程中存在有可能中断异常的任务,比如Sleep,将会出现shutdownNow()之后抛出异常的情况,此时任务将会被中断。
//具备返回值
List runnables = executorService.shutdownNow();
/**
* 如果是一个很长的睡眠任务,则会中断
* java.lang.InterruptedException: sleep interrupted
* at java.base/java.lang.Thread.sleep(Native Method)
* at java.base/java.lang.Thread.sleep(Thread.java:339)
* at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
* at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$2(ExecutorServiceTest.java:111)
* at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
* at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
* at java.base/java.lang.Thread.run(Thread.java:834)
* */
for (Runnable runnable : runnables) {
Thread thread = new Thread(runnable);
thread.setName("处理未结束内容");
thread.run();
System.out.println("补偿,继续运行 " );
}
再次强调,shutdownNow() 方法返回一个等待处理的任务列表,由开发者来决定如何处理这些任务。
关闭ExecutorService的最佳实践(这也是Oracle推荐的)是使用这两个方法与 awaitTermination() 方法相结合。比如下面的代码:
executorService.shutdown();
// 最佳实践 shutdownNow 和 awaitTermination 方法结合使用
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
// 防止当前执行操作线程被其他线程中断
executorService.shutdownNow();
}
使用这种方法,ExecutorService将首先停止接受新的任务,然后在指定的时间内等待所有任务的完成。如果这个时间过了,执行就会立即停止。
5. The Future Interface
上面介绍了submit() 和 invokeAll() 将会返回对象或者返回 Future 类型的集合,通过 Future 可以实现当前线程等待线程池的任务执行回调通知结果,或者通过Future 获取当前任务的执行状态。
这一套API属于典型的NIO异步阻塞的线程模型,Future接口提供了一个特殊的阻塞方法get(),它返回Callable任务的实际执行结果,如果是Runnable任务则返回null。
Future future = executorService.submit(callableTask);
String result = null;
try {
result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
在任务仍在运行时调用get()方法将导致执行阻塞,直到任务正确执行且结果可用。另外由于get()方法造成的阻塞时间很长,应用程序的性能会下降,如果产生的数据不是很关键,可以通过使用超时来避免这种问题。
String result = future.get(200, TimeUnit.MILLISECONDS);
如果执行时间超过了指定的时间(在本例中是200毫秒),将抛出一个TimeoutException。PS:我们可以使用 isDone 判断当前分配的任务是否已经执行完成。
List> futures = executorService.invokeAll(callableTasks);
for (Future future : futures) {
System.out.println("是否完成:"+ future.isDone());
System.out.println("收到回调:" + future.get());
}
/**
* 判断是否完成
* pool-1-thread-1 callableTask
* pool-1-thread-1 callableTask * pool-1-thread-1 callableTask * 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* */
Future接口还提供了用 cancel() 方法取消任务的执行,并用 isCancelled() 方法检查取消情况。为了实验这个效果,这里我们需要把前面提到的Callable的任务延长执行时间:
Future submit1 = executorService2.submit(callableTask3);
submit1.cancel(true);
// 如果一个任务 cancel 之后进行get,会抛出异常
/*
Exception in thread "main" java.util.concurrent.CancellationException
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.assignTaskToExecutor(ExecutorServiceTest.java:171)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.main(ExecutorServiceTest.java:20)
* */
// System.out.println("取消执行任务"+submit1.get());
System.out.println("是否取消执行任务:"+ submit1.isCancelled());
/**
* 是否取消执行任务:true
* */
6. The ScheduledExecutorService Interface
ScheduledExecutorService 线程池可以存放一些定期或者延期执行的任务。依然建议使用Executors工厂构建ScheduledExecutorService:
ScheduledExecutorService executorService = Executors
.newSingleThreadScheduledExecutor();
要在一个固定(时间)延迟后安排并执行任务,使用 ScheduledExecutorService 的 scheduled() 方法。 scheduled( )方法允许你执行Runnable或Callable任务。
Callable scheduleCall = () -> {
System.out.println(Thread.currentThread().getName()+" callableTask");
TimeUnit.MILLISECONDS.sleep(300);
return "Task's execution "+Thread.currentThread().getName();
};
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
Future resultFuture =
scheduledExecutorService.schedule(scheduleCall, 1, TimeUnit.SECONDS);
System.out.println("resultFuture => "+ resultFuture.get());
scheduledExecutorService.shutdown();
/**
* pool-3-thread-1 callableTask resultFuture => Task's execution pool-3-thread-1 * */
scheduleAtFixedRate()方法让我们在一个固定的延迟时间后运行一个任务。上面的代码在执行callableTask之前延迟了一秒。
下面的代码块将在100毫秒的初始延迟后运行一个任务。此后它将每隔450毫秒运行一次相同的任务。
如果此时存在关闭操作,这种“周期”任务就会被中断。
//下面的代码块将在100毫秒的初始延迟后运行一个任务。此后,它将每隔**450毫秒**运行一次相同的任务
scheduledExecutorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);
try {
if (!scheduledExecutorService.awaitTermination(4000, TimeUnit.MILLISECONDS)) {
scheduledExecutorService.shutdownNow();
}
} catch (InterruptedException e) {
scheduledExecutorService.shutdownNow();
}/**
pool-3-thread-1 runnableTask
pool-3-thread-2 runnableTask
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
*/
如果处理器需要比 scheduleAtFixedRate() 方法的周期参数更多的时间来运行一个指定的任务,ScheduledExecutorService将等待,直到当前任务完成后再开始下一个。
如果有必要在任务的迭代之间有一个固定长度的延迟,应该使用scheduleWithFixedDelay()。例如,下面的代码将保证在当前执行结束和另一个执行开始之间有150毫秒的停顿:
// 下面的代码将保证在当前执行结束和另一个执行开始之间有150毫秒的停顿
scheduledExecutorService.scheduleWithFixedDelay(runnableTask, 100, 150, TimeUnit.MILLISECONDS);
注意任务的周期执行将在ExecutorService终止时或在任务执行期间抛出异常时结束。比如前面介绍的优雅的关闭线程池的方式,遇到周期任务中有可能的中断操作会导致线程进行响应中断而结束执行。
7. ExecutorService vs Fork/Join
Java7之后出现的Fork/Join 框架成为了ExecutorService框架的进一步替代者。
虽然fork/join的操作使得开发并发编程程序变简单,同时减少了开发者的执行权控制。但是注意这并不是任何时候都是正确的。ExecutorService让开发者有能力控制生成的线程数量以及应该由独立线程运行的任务的粒度。ExecutorService的最佳用例是处理独立的任务,比如根据 “一个线程一个任务 “的方案处理事务或请求,比如多个Sheet的数据使用多个线程并行解析和处理。
相较之下,从Oracle的文档来看fork/join的设计更像是是为了加快那些可以递归地分解成小块的工作。
Fork/Join (The Java™ Tutorials > Essential Java Classes > Concurrency) (oracle.com)
尽管ExecutorService相对简单,但也有一些常见的陷阱。
最后让我们来总结一下:
- 未使用的线程池会一直存活:参见第4节中关于如何关闭ExecutorService的详细解释。
- 谨慎考虑使用固定长度的线程池时,避免错误的线程池容量。确定应用程序需要多少个线程来有效运行任务是非常重要的。过大的线程池会导致不必要的开销,而这些线程大多处于等待模式。太少的线程会使应用程序看起来没有反应,因为队列中的任务有很长的等待时间。
- 在任务取消后调用一个Future的get()方法会怎么样?试图获取一个已经取消的任务的结果会触发一个CancellationException。
- 用Future的get()方法意外地进行长时间阻塞。我们应该使用超时来避免意外的等待。
8. 代码
原文的代码可以在GitHub仓库中找到:
tutorials/core-java-modules/core-java-concurrency-simple at master · eugenp/tutorials (github.com)
下面是个人胡乱折腾的案例:
public class ExecutorServiceTest
{
public static void main(String[] args) throws ExecutionException, InterruptedException {
// newExecutorService();
assignTaskToExecutor();
}
/**
* 不推荐使用除开
*/
@Deprecated
private static ExecutorService newExecutorService() {
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
return executorService;
}
public static void assignTaskToExecutor() throws ExecutionException, InterruptedException {
Runnable runnableTask = () -> {
try {
System.out.println(Thread.currentThread().getName()+" runnableTask");
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Callable callableTask = () -> {
System.out.println(Thread.currentThread().getName()+" callableTask");
TimeUnit.MILLISECONDS.sleep(300);
return "Task's execution "+Thread.currentThread().getName();
};
List> callableTasks = new ArrayList();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);
ExecutorService executorService = newExecutorService();
executorService.execute(runnableTask);
Future> submit = executorService.submit(runnableTask);
// runable返回结果为null
Object o = submit.get();
System.out.println(o);
/**
pool-1-thread-1 runnableTask
pool-1-thread-1 runnableTask
Runnable的返回结果为null
null
*/
for (Callable task : callableTasks) {
Future> callResult = executorService.submit(task);
Object callRes = callResult.get();
System.err.println(callRes);
}/**
pool-1-thread-1 callableTask
Task's execution
pool-1-thread-1 callableTask
Task's execution
pool-1-thread-1 callableTask
Task's execution
*/
System.out.println("n");
String result = executorService.invokeAny(callableTasks);
System.out.println("invokeAny => "+result);
/**
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
Task's executionpool-1-thread-1
*/
System.out.println("n");
System.out.println("n");
List> futures = executorService.invokeAll(callableTasks);
for (Future future : futures) {
System.out.println("是否完成:"+ future.isDone());
System.out.println("收到回调:" + future.get());
}/**
callAble 的接口实现回调结果:
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
pool-1-thread-1 callableTask
收到回调Task's execution pool-1-thread-1
收到回调Task's execution pool-1-thread-1
收到回调Task's execution pool-1-thread-1
*/
/**
* 判断是否完成
* pool-1-thread-1 callableTask
* pool-1-thread-1 callableTask
* pool-1-thread-1 callableTask
* 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* 是否完成:true
* 收到回调:Task's execution pool-1-thread-1
* */
// 关闭线程池
// executorService.shutdown();
// Runnable runnableTask2 = () -> {
// try {
// System.out.println(Thread.currentThread().getName()+" runnableTask");
// TimeUnit.MILLISECONDS.sleep(30000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// };
// executorService.execute(runnableTask2);
//具备返回值
// List runnables = executorService.shutdownNow();
/**
* 如果是一个很长的睡眠任务,则会中断
* java.lang.InterruptedException: sleep interrupted
* at java.base/java.lang.Thread.sleep(Native Method)
* at java.base/java.lang.Thread.sleep(Thread.java:339)
* at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
* at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$2(ExecutorServiceTest.java:111)
* at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
* at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
* at java.base/java.lang.Thread.run(Thread.java:834)
* */
// for (Runnable runnable : runnables) {
// Thread thread = new Thread(runnable);
// thread.setName("处理未结束内容");
// thread.run();
// System.out.println("补偿,继续运行 " );
// }
// executorService.shutdown();
System.out.println("n");
System.out.println("n");
// 延长等待时间的回调任务
ExecutorService executorService2 = newExecutorService();
Callable callableTask3 = () -> {
System.out.println(Thread.currentThread().getName()+" callableTask");
// TimeUnit.MILLISECONDS.sleep(300);
// 延长等待时间
TimeUnit.MILLISECONDS.sleep(30000);
return "Task's execution "+Thread.currentThread().getName();
};
Future submit1 = executorService2.submit(callableTask3);
submit1.cancel(true);
// 如果一个任务 cancel 之后进行get,会抛出异常
/*
Exception in thread "main" java.util.concurrent.CancellationException
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.assignTaskToExecutor(ExecutorServiceTest.java:171)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.main(ExecutorServiceTest.java:20)
* */
// System.out.println("取消执行任务"+submit1.get());
System.out.println("是否取消执行任务:"+ submit1.isCancelled());
/**
* 是否取消执行任务:true
* */
// 最佳实践 shutdownNow 和 awaitTermination 方法结合使用
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
// 下面的代码在执行callableTask之前延迟了一秒。
System.out.println("n");
System.out.println("n");
Callable scheduleCall = () -> {
System.out.println(Thread.currentThread().getName()+" callableTask");
TimeUnit.MILLISECONDS.sleep(300);
return "Task's execution "+Thread.currentThread().getName();
};
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
Future resultFuture =
scheduledExecutorService.schedule(scheduleCall, 1, TimeUnit.SECONDS);
System.out.println("resultFuture => "+ resultFuture.get());
/**
*
pool-3-thread-1 callableTask
resultFuture => Task's execution pool-3-thread-1
* */
System.out.println("n");
//下面的代码块将在100毫秒的初始延迟后运行一个任务。此后,它将每隔**450毫秒**运行一次相同的任务
scheduledExecutorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);
// 下面的代码将保证在当前执行结束和另一个执行开始之间有150毫秒的停顿
scheduledExecutorService.scheduleWithFixedDelay(runnableTask, 100, 150, TimeUnit.MILLISECONDS);
/*
注意任务的周期执行将在ExecutorService**终止**时或在任务执行期间**抛出异常**时结束。
pool-3-thread-1 runnableTask
pool-3-thread-2 runnableTask
pool-3-thread-3 runnableTask
pool-3-thread-2 runnableTask
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
* */
try {
if (!scheduledExecutorService.awaitTermination(4000, TimeUnit.MILLISECONDS)) {
scheduledExecutorService.shutdownNow();
}
} catch (InterruptedException e) {
scheduledExecutorService.shutdownNow();
}/**
pool-3-thread-1 runnableTask
pool-3-thread-2 runnableTask
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
*/
executorService2.shutdown();
}
}
附录
阿里巴巴手册如何介绍
.【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异 常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
3)ScheduledThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net