繁体   English   中英

Java主线程终止时如何管理工作线程生命周期?

[英]How to manage worker thread lifecycles when main Java thread terminates?

我想要实现以下目标:当我的应用程序启动时,主线程将启动应在后台运行的1+个工作线程,并定期在后台进行操作。 这些不应阻塞主线程:一旦main启动了工作程序,它将继续做自己的事情,直到:

  1. 主线程完成(正常的应用程序终止)-在使用命令行实用程序的情况下,这是到达main(String[])方法的末尾时; 对于Swing GUI,可能是用户选择File >> Exit菜单等时。
  2. 操作系统抛出kill命令(SIGKILL等)
  3. 主线程中发生意外的,未捕获的异常,有效地杀死了它(这只是上面#1的不礼貌的版本)

从主线程启动/提交后,我希望所有工作线程( Runnable )基本上都有自己的生命周期,并且独立于主线程而存在。 但是,如果主线程在任何时间死掉,我希望能够阻塞(如果可能的话)主线程,直到所有工作线程都关闭, 然后 “允许”主线程死掉。

到目前为止,我一直尽力而为,尽管我知道我在这里和那里都缺少片段:

public class MainDriver {
    private BaneWorker baneWorker;

    private ExecutorService executor = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        MainDriver driver = new MainDriver();
        driver.run();

        // We've now reached the end of the main method. All workers should block while they shutdown
        // gracefully (if at all possible).
        if(executor.awaitTermination(30, TimeUnit.SECONDS))
            System.out.println("Shutting down...");
        else {
            System.out.println("Forcing shut down...");
            executor.shutdownNow();
        }
    }

    private void run() {
        // Start all worker threads.
        baneWorker = new BaneWorker(Thread.currentThread());
        // More workers will be used once I get this simple example up and running...

        executor.submit(baneWorker);
        // Eventually submit the other workers here as well...

        // Now start processing. If command-line utility, start doing whatever the utility
        // needs to do. If Swing GUI, fire up a parent JFrame and draw the application to the
        // screen for the user, etc.
        doStuff();
    }

    private void doStuff() {
        // ??? whatever
    }
}

public class BaneWorker implements Runnable {
    private Timer timer;

    private TimerTask baneTask;

    private Thread mainThread;

    public BaneWorker(Thread mainThread) {
        super();

        this.mainThread = mainThread;
    }

    @Override
    public void run() {
        try {
            timer = new Timer();

            baneTask = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("When the main thread is ashes...");
                }
            };

            // Schedule the baneTask to kick off every minute starting now.
            timer.scheduleAtFixedRate(baneTask, new Date(), 60 * 1000);
        } catch(InterruptedException interrupt) {
            // Should be thrown if main thread dies, terminates, throws an exception, etc.
            // Should block main thread from finally terminating until we're done shutting down.
            shutdown();
        }
    }

    private void shutdown() {
        baneTask.cancel();

        System.out.println("...then you have my permission to die.");

        try {
            mainThread.join();
        } catch(InterruptedException interrupt) {
            interrupt.printStackTrace;
        }
    }
}

我在这里吗? 我需要进行哪些更改才能按照需要的方式进行此工作? 我是Java并发的新手,正在尽最大努力正确使用Concurrency API,但有些困难。 有任何想法吗? 提前致谢!

主线程必须向工作线程发出终止信号(通常是通过使用标志来实现),然后它应在每个线程上调用join来等待其终止。 在这里看看: Java:如何使用Thread.join

您可以使用Runtime.addShutdownHook来注册未启动的线程,该线程在JVM终止,系统关闭等情况下执行。此代码可以自行清理,或者通知正在运行的守护程序线程完成其工作。 任何这样的清理代码必须相对较快,因为在许多系统上,程序在被强制终止之前只有有限的时间进行清理。

也许您还可以考虑使后台线程守护程序线程成为线程 这样,当main完成时,它们将不会阻塞JVM,并且在清理阶段仍将继续运行。

请注意,您无法截获SIGKILL-此信号被设计为不可避免且立即发生。 但它应与SIGTERM,SIGHUP和类似信号一起使用。


更新:您可以轻松创建运行守护程序线程的ExecutorService 您只需要创建一个合适的ThreadFactory

public static class DaemonFactory
        implements ThreadFactory
    {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    }

比您创建一个ExecutorService类的

public static void main(String argv[])
    throws Exception
{
    ExecutorService es 
        = Executors.newCachedThreadPool(new DaemonFactory());
    //                                  ^^^^^^^^^^^^^^^^^^^
    es.submit(new Callable<Object>() {
        public Object call() throws Exception {
            Thread.sleep(100);
            System.err.println("Daemon: " +
                Thread.currentThread().isDaemon());
            return null;
        }
    });
    // Without this, JVM will terminate before the daemon thread prints the
    // message, because JVM doesn't wait for daemon threads when
    // terminating:
    es.awaitTermination(3, TimeUnit.SECONDS);
}

关于Thread.join() ,您不应尝试在ExecutorService管理的线程上使用它。 执行者负责管理它们。 您没有可靠的方法枚举其线程,执行程序可以根据其配置创建和销毁线程。唯一可靠的方法是调用shutdown(); 然后awaitTermination(...);

如果SIGKILL是Unix的“ kill -9”,您将无能为力。

对于正常的退出,在主目录中使用try / catch / finally。 catch将捕获您的异常,并允许您执行需要做的事情(恢复吗?中止?) finally将使您有个钩子,可以优雅地旋转线程。

快速查看您的代码,我看不到您在哪里跟踪线程实例。 如果您要告诉他们降低速度,则将需要这些。

伪代码:

static Main(...) {
    ArrayList threads = new ArrayList();
    try {
        for (each thread you want to spin up) {
            threads.add(a new Thread())
            }
        }
    catch { assuming all are fatal. }
    finally {
        for(each thread t in threads) {
            t.shutdown();
            t.join(); /* Be prepared to catch (and probably ignore) an exception on this, if shutdown() happens too fast! */
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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