繁体   English   中英

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

[英]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的关闭。 此要求通常会级联到应用程序的多个层。 由于这个原因,大多数应用程序框架都支持某种组件生命周期管理。

其他问题

提供的代码还有其他问题:

  1. while(true || (System.currentTimeMillis()-startTime) < 60000)等价于while (true) (System.currentTimeMillis()-startTime) < 60000条件无效。
  2. 在循环中检查每个Future是否完成称为“忙等待” ,通常不鼓励这样做,因为它会消耗大量 CPU。 如果您在运行此程序时查看 CPU 监视器,您会注意到使用率激增,尽管该程序除了等待之外并没有做太多事情。 如果可能,通常最好使用阻塞调用线程直到满足条件的方法。 在这种情况下, Future.get正是这样做的。
  3. 不建议捕获和忽略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.

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