正常关闭线程和执行器

2022-09-01 06:32:42

下面的代码段试图解决这个问题。

代码将永久循环,并检查是否有任何挂起的请求需要处理。如果有的话,它会创建一个新线程来处理请求并将其提交给执行程序。完成所有线程后,它会休眠 60 秒,并再次检查挂起的请求。

public static void main(String a[]){
    //variables init code omitted
    ExecutorService service = Executors.newFixedThreadPool(15);
    ExecutorCompletionService<Long> comp = new ExecutorCompletionService<Long>(service);
    while(true){
        List<AppRequest> pending = service.findPendingRequests();
        int noPending = pending.size();
        if (noPending > 0) {
            for (AppRequest req : pending) {
                Callable<Long> worker = new RequestThread(something, req);
                comp.submit(worker);
            }
        }
        for (int i = 0; i < noPending; i++) {
            try {
                Future<Long> f = comp.take();
                long name;
                try {
                    name = f.get();
                    LOGGER.debug(name + " got completed");
                } catch (ExecutionException e) {
                    LOGGER.error(e.toString());
                }
            } catch (InterruptedException e) {
                LOGGER.error(e.toString());
            }
        }
        TimeUnit.SECONDS.sleep(60);
    }

  }

我的问题是这些线程完成的大部分处理都与数据库有关。该程序将在Windows机器上运行。当有人尝试关闭或注销计算机时,这些线程会发生什么情况?如何优雅地关闭正在运行的线程以及执行器?


答案 1

执行器服务的典型有序关闭可能如下所示:

final ExecutorService executor;

Runtime.getRuntime().addShutdownHook(new Thread() {
    public void run() {
        executor.shutdown();
        if (!executor.awaitTermination(SHUTDOWN_TIME)) { //optional *
            Logger.log("Executor did not terminate in the specified time."); //optional *
            List<Runnable> droppedTasks = executor.shutdownNow(); //optional **
            Logger.log("Executor was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed."); //optional **
        }
    }
});

*您可以记录执行程序在等待您愿意等待的时间后仍有任务要处理。
**您可以尝试强制执行程序的工作线程放弃其当前任务,并确保它们不会启动任何剩余的任务。

请注意,当用户向进程发出中断或仅包含守护程序线程时,上述解决方案将起作用。相反,如果 包含尚未完成的非守护进程线程,则 JVM 不会尝试关闭,因此不会调用关闭钩子。javaExecutorServiceExecutorService

如果尝试将进程作为离散应用程序生命周期(不是服务)的一部分关闭,则不应将关闭代码放在关闭挂钩内,而应放在程序设计为终止的适当位置。


答案 2

Java 并发实践》一书指出:

7.4. JVM 关机

JVM 可以有序或突然关闭。当最后一个“正常”(非守护程序)线程终止,有人调用System.exit或通过其他特定于平台的方式(例如发送SIGINT或按Ctrl-C)时,将启动有序关闭。[...]

7.4.1. 关闭钩子

在有序关机中,JVM 首先启动所有已注册的关机挂接。Shutdown hooks 是向 Runtime.addShutdownHook 注册的未启动线程。JVM 不保证关闭挂钩的启动顺序。如果任何应用程序线程(守护程序或非守护程序)在关闭时仍在运行,它们将继续与关闭进程同时运行。当所有关闭挂接都完成后,如果 runFinalizersOnExit 为真,JVM 可能会选择运行终结器,然后停止。JVM 不会尝试停止或中断在关闭时仍在运行的任何应用程序线程。当JVM最终停止时,它们会突然终止。如果关闭钩子或终结器未完成,则有序关闭过程将“挂起”,并且必须突然关闭JVM。[...]

重要的部分是,“JVM不会尝试停止或中断在关闭时仍在运行的任何应用程序线程;当JVM最终停止时,它们会突然终止。因此,我认为与数据库的连接将突然终止,如果没有关闭钩子可以进行优雅的清理(如果您使用的是框架,它们通常会提供此类关闭钩子)。根据我的经验,与数据库的会话可以一直保持,直到数据库超时,当应用程序在没有此类钩子的情况下终止时,数据库等。