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

时间:2013-07-04 14:26:40

标签: java multithreading concurrency executorservice

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

  1. 主线程完成(正常的应用程序终止) - 在命令行实用程序的情况下,这是到达main(String[])方法的结尾时;对于Swing GUI,可以是用户选择File >> Exit菜单等
  2. 操作系统抛出kill命令(SIGKILL等)
  3. 在主线程中发生意外的,未捕获的异常,有效地将其杀死(这只是上面#1的一个非人类版本)
  4. 从主线程启动/提交后,我希望所有工作线程(Runnable s)基本上都有自己的生命周期,并且独立于主线程存在。但是,如果主线程在任何时候死亡,我希望能够阻止(如果可能的话)主线程,直到所有工作人员完成关闭,然后然后“允许”主线程线程死了。

    到目前为止,我是最好的尝试,虽然我知道我在这里和那里都缺少一些东西:

    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并发的新手,我正在尽我所能正确使用并发API,但有点磕磕绊绊。有任何想法吗?提前谢谢!

3 个答案:

答案 0 :(得分:1)

主线程必须通知工作线程终止(通常这只是通过使用标志来实现)然后它应该在每个线程上调用join以等待它们的终止。看看这里:Java: How to use Thread.join

答案 1 :(得分:1)

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

也许您也可以考虑制作后台帖子daemon threads。然后,当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(...);

答案 2 :(得分:0)

如果SIGKILL是unix“kill -9”,你就无能为力了。

对于优雅的退出,请在主体中使用try / catch / finally。 catch将捕获您的异常并允许您执行需要执行的操作(recover?abort?)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! */
        }
    }