简体   繁体   English

当 Enclosure class 准备好进行 GC 时,优雅关闭 ThreadPoolExecutor

[英]Graceful shutdown of ThreadPoolExecutor when Enclosing class is ready for GC

I am using ExecutorService in my class to async a few Callable tasks and then once all the tasks are complete, complete the parent process.我在我的 class 中使用 ExecutorService 来异步一些 Callable 任务,然后在所有任务完成后,完成父进程。 Something like this像这样的东西

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);
}


}

It is working as expected.它按预期工作。

The above example is just from a scratch file but i have something very similar.上面的例子只是来自一个临时文件,但我有一些非常相似的东西。 now i have kept the ThreadPoolExecutor as an instance object as it is being called multiple times and i do not want to create a new executor for each call.现在我将 ThreadPoolExecutor 保留为实例 object 因为它被多次调用,我不想为每个调用创建一个新的执行程序。 I would like to know are there any implications if i do not terminate or shutdown the executor when the main service class is terminated/ready for GC.我想知道,如果在主要服务 class 终止/准备好进行 GC 时我不终止或关闭执行程序,会有什么影响。 I wanted to use the finalize method but it is deprecated now.我想使用 finalize 方法,但现在已弃用。 So in this case what is the best approach to shutdown the executor when the enclosing class is GC without using finalize method?那么在这种情况下,当封闭的 class 是 GC 而不使用 finalize 方法时,关闭执行程序的最佳方法是什么?

Shutting down the ExecutorService关闭ExecutorService

Yes, it is important to shutdown the executor when it is no longer needed.是的,在不再需要执行程序时关闭它很重要。 In this example (once the NullPointerException is fixed), when the main method exits, the process does not terminate because the thread pool has not been shut down.在这个例子中(一旦NullPointerException被修复),当main方法退出时,进程并没有终止,因为线程池还没有关闭。 As the documentation for Executors.newFixedThreadPool says:正如Executors.newFixedThreadPool的文档所说:

The threads in the pool will exist until it is explicitly shutdown .池中的线程将一直存在,直到显式shutdown

You are correct that the use of the finalize method is deprecated and will be removed in a future version of Java.您是正确的,不推荐使用finalize方法,并将在 Java 的未来版本中删除。 The best approach to shutting it down is to explicitly call shutdown when it is no longer needed, in a finally block to ensure it happens regardless of whether execution completes successfully or throws:关闭它的最佳方法是在不再需要它时显式调用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();
    }
}

In Java 19 or later, you can also use the try-with-resources construct:在 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();
        }
    }
}

In a more complete application, the application component that creates the ExecutorService may require its own shutdown method to trigger the shutdown of the ExecutorService .在更完整的应用程序中,创建ExecutorService的应用程序组件可能需要自己的关闭方法来触发ExecutorService的关闭。 It's common for this requirement to cascade through several layers of the application.此要求通常会级联到应用程序的多个层。 Most application frameworks support some sort of component lifecycle management for this reason.由于这个原因,大多数应用程序框架都支持某种组件生命周期管理。

Other issues其他问题

There are other issues with the code provided:提供的代码还有其他问题:

  1. while(true || (System.currentTimeMillis()-startTime) < 60000) is equivalent to while (true) . while(true || (System.currentTimeMillis()-startTime) < 60000)等价于while (true) The (System.currentTimeMillis()-startTime) < 60000 condition has no effect. (System.currentTimeMillis()-startTime) < 60000条件无效。
  2. Checking each Future for completion in a loop is called "busy waiting" , and is usually discouraged because it consumes a high level of CPU.在循环中检查每个Future是否完成称为“忙等待” ,通常不鼓励这样做,因为它会消耗大量 CPU。 If you watch a CPU monitor while running this program, you will notice a spike in usage, despite the fact that the program doesn't do very much other than wait.如果您在运行此程序时查看 CPU 监视器,您会注意到使用率激增,尽管该程序除了等待之外并没有做太多事情。 It is generally preferable to use a method that blocks the calling thread until the condition is satisfied, when possible.如果可能,通常最好使用阻塞调用线程直到满足条件的方法。 In this case, Future.get does exactly that.在这种情况下, Future.get正是这样做的。
  3. Catching and ignoring InterruptedException is not recommended, as it defeats the purpose of interrupting a thread as a signal to terminate.不建议捕获和忽略InterruptedException ,因为它破坏了将线程中断作为终止信号的目的。 In this case, I would allow it to propagate and terminate the program.在这种情况下,我会允许它传播和终止程序。

Putting these together, here is the alternative implementation:将这些放在一起,这是替代实现:

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();
    }
}

This preserves the behaviour of printing the results all at once at the end, rather than printing each result in order as it becomes available.这保留了在最后一次打印所有结果的行为,而不是在每个结果可用时按顺序打印。 If the latter behaviour is acceptable, then you can simplify this further into a single loop.如果后一种行为是可以接受的,那么您可以将其进一步简化为一个循环。

I would like to know are there any implications if i do not terminate or shutdown the executor when the main service class is terminated/ready for GC.我想知道,如果在主要服务 class 终止/准备好进行 GC 时我不终止或关闭执行程序,会有什么影响。

You certainly should be using a single ExecutorService .您当然应该使用单个ExecutorService That's the whole point of it.这就是它的全部意义所在。 You should be submitting your jobs to the service until you are done and then you should immediately shutdown the service.您应该在完成之前将您的作业提交给该服务,然后您应该立即关闭该服务。

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();

Also you really done need the done checking look.此外,您确实需要完成检查外观。 Going through your futures and calling get() will do that for you:浏览你的未来并调用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
}

Also, I don't think you should be using the term GC.另外,我认为您不应该使用术语 GC。 You should talk about the main method exiting but we really rarely have to worry about GC issues unless your goal really is to reduce the object bandwidth of a high performance program or if you have a memory leak.您应该谈论退出的主要方法,但我们很少需要担心 GC 问题,除非您的目标确实是降低高性能程序的 object 带宽,或者如果您有 memory 泄漏。

One additional comment.补充一条评论。 You should never initialize static fields in an instance constructor because if you call new Scratch() twice, it will overwrite the static field.您永远不应该在实例构造函数中初始化 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM