[英]Spring schedule graceful shutdown not working when using a cron scheduled
[英]Graceful shutdown of ThreadPoolExecutor when Enclosing class is ready for GC
我在我的 class 中使用 ExecutorService 来异步一些 Callable 任务,然后在所有任务完成后,完成父进程。 像这样的东西
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
class SampleService implements Callable<String>{
private String dayOfWeek;
public SampleService(String dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
@Override
public String call() throws Exception {
if("Holiday".equals(dayOfWeek)){
throw new RuntimeException("Holiday is not valid day of week");
} else{
Random random = new Random();
int sleepTime = random.nextInt(60000);
Thread.sleep(sleepTime);
System.out.println("Thread "+dayOfWeek+" slept for "+sleepTime);
return dayOfWeek+" is complete";
}
}
}
class Scratch {
static ExecutorService executor = null;
public Scratch() {
executor = Executors.newFixedThreadPool(8);
}
public static void main(String[] args) {
List<String> days = Arrays.asList("Monday","Tuesday","Wednesday","Thursday","Friday","Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p,executor))
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
while(true || (System.currentTimeMillis()-startTime) < 60000){
boolean complete = true;
for(Future<String> future : completables){
complete = complete && future.isDone(); // check if future is done
}
if(complete){
System.out.println(" all tasks complete");
break;
}
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken to get response from all threads "+ (endTime - startTime));
try{
for(Future<String> future : completables){
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Future<String> createFuture(String p, ExecutorService executor) {
SampleService service = new SampleService(p);
return executor.submit(service);
}
}
它按预期工作。
上面的例子只是来自一个临时文件,但我有一些非常相似的东西。 现在我将 ThreadPoolExecutor 保留为实例 object 因为它被多次调用,我不想为每个调用创建一个新的执行程序。 我想知道,如果在主要服务 class 终止/准备好进行 GC 时我不终止或关闭执行程序,会有什么影响。 我想使用 finalize 方法,但现在已弃用。 那么在这种情况下,当封闭的 class 是 GC 而不使用 finalize 方法时,关闭执行程序的最佳方法是什么?
ExecutorService
是的,在不再需要执行程序时关闭它很重要。 在这个例子中(一旦NullPointerException
被修复),当main
方法退出时,进程并没有终止,因为线程池还没有关闭。 正如Executors.newFixedThreadPool
的文档所说:
池中的线程将一直存在,直到显式
shutdown
。
您是正确的,不推荐使用finalize
方法,并将在 Java 的未来版本中删除。 关闭它的最佳方法是在不再需要它时显式调用shutdown
,在finally
块中以确保无论执行成功完成还是抛出,它都会发生:
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
// code omitted for brevity
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
executor.shutdown();
}
}
在 Java 19 或更高版本中,您还可以使用try-with-resources构造:
public static void main(String[] args) {
try (ExecutorService executor = Executors.newFixedThreadPool(8)) {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
// code omitted for brevity
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在更完整的应用程序中,创建ExecutorService
的应用程序组件可能需要自己的关闭方法来触发ExecutorService
的关闭。 此要求通常会级联到应用程序的多个层。 由于这个原因,大多数应用程序框架都支持某种组件生命周期管理。
提供的代码还有其他问题:
while(true || (System.currentTimeMillis()-startTime) < 60000)
等价于while (true)
。 (System.currentTimeMillis()-startTime) < 60000
条件无效。Future
是否完成称为“忙等待” ,通常不鼓励这样做,因为它会消耗大量 CPU。 如果您在运行此程序时查看 CPU 监视器,您会注意到使用率激增,尽管该程序除了等待之外并没有做太多事情。 如果可能,通常最好使用阻塞调用线程直到满足条件的方法。 在这种情况下, Future.get
正是这样做的。InterruptedException
,因为它破坏了将线程中断作为终止信号的目的。 在这种情况下,我会允许它传播和终止程序。将这些放在一起,这是替代实现:
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
for (Future<String> future : completables) {
try {
future.get();
} catch (ExecutionException e) {
// ignore it here, it will be printed below
}
}
System.out.println(" all tasks complete");
long endTime = System.currentTimeMillis();
System.out.println("Time taken to get response from all threads " + (endTime - startTime));
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
}
} finally {
executor.shutdown();
}
}
这保留了在最后一次打印所有结果的行为,而不是在每个结果可用时按顺序打印。 如果后一种行为是可以接受的,那么您可以将其进一步简化为一个循环。
我想知道,如果在主要服务 class 终止/准备好进行 GC 时我不终止或关闭执行程序,会有什么影响。
您当然应该使用单个ExecutorService
。 这就是它的全部意义所在。 您应该在完成之前将您的作业提交给该服务,然后您应该立即关闭该服务。
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p,executor))
.collect(Collectors.toList());
// this will shutdown the executor while the submitted jobs run in the background
executor.shutdown();
此外,您确实需要完成检查外观。 浏览你的未来并调用get()
将为你做到这一点:
// you don't need this
for(Future<String> future : completables){
// also don't use this sort of logic. works fine. very hard to read.
complete = complete && future.isDone(); // check if future is done
}
另外,我认为您不应该使用术语 GC。 您应该谈论退出的主要方法,但我们很少需要担心 GC 问题,除非您的目标确实是降低高性能程序的 object 带宽,或者如果您有 memory 泄漏。
补充一条评论。 您永远不应该在实例构造函数中初始化 static 字段,因为如果您调用new Scratch()
两次,它将覆盖 static 字段。
class Scratch {
// should be initialized here or in static { } block
// always use final if you can
private final static ExecutorService executor = Executors.newFixedThreadPool(8);
public Scratch() {
// you should not initialize static fields in an instance constructor
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.