避免在多线程代码中使用Shutdown Flag

时间:2019-05-09 21:43:05

标签: java multithreading concurrency executorservice

我有以下代码:

private static final AtomicBoolean shutdown = new AtomicBoolean(false);

public static void main(final String... args) {

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        shutdown.set(true);
        executorService.shutdown();
        try {
            executorService.awaitTermination(SHUTDOWN_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
        } catch (InterruptedException e) {
               executorService.shutdownNow();
        }
    }));

    executorService = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 2; i++) {
        executorService.execute(create());
    }
}

private static Runnable create() {
    return new Runnable() {
        @Override
        public void run() {
            while (!shutdown.get()) {
                try {
                    Thread.sleep(5000);
                    System.out.println("Hatella" + Thread.currentThread().getName());
                } catch (Throwable t) {
                }
            }
        }
    };
}

这段代码可以很好地工作,但是我想用更简单的方式编写这段代码,这样我就不必在每个while循环中检查shutdown标志的状态。不知道该怎么做才能解决此问题并实现相同的目的。

2 个答案:

答案 0 :(得分:1)

shutdown()仅会使ExecutorService不接受更多任务,但是它将继续执行所有待处理的任务直到最后。由于您实际上想停止执行任务,因此应首先使用shutdownNow(),它会发送一个中断信号。

public static void main(final String... args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        executorService.shutdownNow();
        try {
            executorService.awaitTermination(
                SHUTDOWN_TIMEOUT.getSeconds(),TimeUnit.SECONDS);
        } catch (InterruptedException e) {}
    }));

    for (int i = 0; i < 2; i++) {
        executorService.execute(create());
    }
}

private static Runnable create() {
    return () -> {
        while(!Thread.interrupted()) {
            try {
                Thread.sleep(5000);
                System.out.println("Hatella" + Thread.currentThread().getName());
            }
            catch(InterruptedException ex) {
                break;
            }
            catch (Throwable t) {
            }
        }
        System.out.println("thread exit " + Thread.currentThread().getName());
    };
}

不仅可以通过Thread.interrupted()查询中断标志,还可以使诸如Thread.sleep(…)之类的阻塞操作提前终止,并通过InterruptedException报告情况。在这两种情况下,当Thread.interrupted()返回true或引发InterruptedException时,中断状态都会被重置,因此至关重要的是要么立即对其作出反应,要么记住您已收到它。因此,在上面的示例中,catch(InterruptedException ex)包含一个break,以结束循环。

但是如图所示,中断不会终止线程,但可以对其进行反应,例如在需要之前清理,然后退出。

请注意,当只有冗长的操作是阻止操作时,您根本不需要手动轮询中断状态,例如以下也可以工作:

private static Runnable create() {
    return () -> {
        while(true) {
            try {
                Thread.sleep(5000);
                System.out.println("Hatella" + Thread.currentThread().getName());
            }
            catch(InterruptedException ex) {
                System.out.println("got "+ex+", "+Thread.interrupted());
                break;
            }
            catch (Throwable t) {
            }
        }
        System.out.println("thread exit");
    };
}

由于此代码不会通过Thread.interrupted()来检查并重置中断状态,因此该信号将持续到下一次调用Thread.sleep时为止,该调用将很快显示为立即响应,因为两次sleep调用之间执行的代码很短。

答案 1 :(得分:0)

A)参见Turning an ExecutorService to daemon in Java。守护程序线程将在技术上回答所陈述的问题(无需轮询“关闭”变量),但在任何有状态的上下文中都可能是 bad 想法,因为该线程将在操作过程中停止,而不会发出警告JVM(所有非守护进程线程完成后)。

executorService = Executors.newFixedThreadPool(2, r -> {
            Thread t = Executors.defaultThreadFactory().newThread();
            t.setDaemon(true);
            return t;
        });

B)现实世界中的另一种选择(空闲线程可能阻塞或休眠某物)是仅在shutdown出现的InterruptedException上检查executorService.shutdownNow() < / p>