6

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

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

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

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

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,但有点磕磕绊绊。有任何想法吗?提前致谢!

4

3 回答 3

1

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

也许你也可以考虑让你的后台线程守护进程线程。然后它们在完成时不会阻塞 JVM,main并且在清理阶段仍将运行。

请注意,您不能拦截 SIGKILL - 此信号旨在不可避免且立即生效。但它应该适用于 SIGTERM、SIGHUP 和类似的信号。


更新:您可以轻松创建ExecutorService运行守护线程的 s。您只需要创建一个适当的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();then awaitTermination(...);

于 2013-07-04T14:45:41.870 回答
1

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

于 2013-07-04T14:43:53.550 回答
0

如果 SIGKILL 是一个 unix “kill -9”,那么你无能为力。

对于优雅的退出,在你的 main 中使用 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! */
        }
    }
于 2013-07-04T14:30:34.470 回答