当线程中断时,为什么ThreadPool会中断其工作者?

时间:2016-05-25 20:10:56

标签: java multithreading threadpool

请看这个例子。我从我的生产项目中拿走它。 Webserver收到一个命令并启动新的Thread,它通过TheadPool开始计算。当用户想要结束计算时,他发送另一个命令来中断这个新线程,并且ThreadPool的工作者正在向下移动。它工作正常,但我不明白为什么。

 public static void main(String[] args) throws Throwable {
        final ExecutorService p = Executors.newFixedThreadPool(2);

        System.out.println("main say: Hello, I'm Main!");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " say: Starting monitor");
                Thread monitor = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try { 
                            while(true) {
                                Thread.sleep(1500);
                                System.out.println(Thread.currentThread().getName() + " say: I'm still here...hahahahah");
                            }
                        } catch (InterruptedException e) {
                            System.out.println(Thread.currentThread().getName() + " say: Bye for now!");
                        }
                    }
                },"monitor");
                monitor.setDaemon(true);
                monitor.start();

                List<Callable<Integer>> threads = new ArrayList<>();
                for (int i = 0; i < 5; i++) {
                    threads.add(new Callable<Integer>() {
                        @Override
                        public Integer call() throws Exception {
                            System.out.println(Thread.currentThread().getName() + " say: Hello!");
                            try {
                                for (int c = 0; c < 5; c++) {
                                    System.out.println(Thread.currentThread().getName() + " say: " + c);
                                    Thread.sleep(500);
                                }
                            } catch (InterruptedException e) {
                                System.out.println(Thread.currentThread().getName() + " say: I'm interrupted :(");
                            }
                            System.out.println(Thread.currentThread().getName() + " say: Bye!");    
                            return 0;
                        }
                    });
                }
                System.out.println(Thread.currentThread().getName() + " say: Starting workers");
                try {
                    p.invokeAll(threads);
                } catch (InterruptedException e) { 
                    System.out.println(Thread.currentThread().getName() + " say: I'm interrupted :(");
                }
                System.out.println(Thread.currentThread().getName() + " say: Bye!"); 
            }
        }, "new thread");
        System.out.println("main say: Starting new thread");
        t.start();
        System.out.println("main say: Waiting a little...");
        Thread.sleep(1250);
        System.out.println("main say: Interrupting new thread");
        t.interrupt();
    //        p.shutdown();
        System.out.println(String.format("main say: Executor state: isShutdown: %s, isTerminated: %s",
                         p.isShutdown(),
                         p.isTerminated()));
        System.out.println("main say: Bye...");
    }

主要问题:当currentThread中断时,为什么ThreadPool会中断其工作者?我在哪里可以了解它的行为?

为什么在这个例子中主线程不会退出,但什么也不做? ThreadPool处于非活动状态,但未被终止,并且正在关闭并且不会处理剩余的任务。

2 个答案:

答案 0 :(得分:2)

  

主要问题:当currentThread中断时,为什么ThreadPool会中断其工作者?我在哪里可以了解它的行为?

你过度概括了。 invokeAll() ExecutorService方法会在中断时取消所有未完成的任务。这在the API docs中有记录。

如果您问“我怎么知道它会这样做”,那么文档就是您的答案。如果你问为什么界面是这样设计的,那么它是有意义的,因为当它被中断时,方法会抛出InterruptedException而不是返回一个值,因此可以合理地假设那些进一步的工作未完成的任务可能会被浪费。

  

为什么在这个例子中主线程不退出,但什么都不做?

“主线程”是从main()开始的那个。此线程 退出,在此之前它还会执行其他几项操作,包括创建,启动和中断线程,以及输出多条消息。它在控件到达main()的末尾时退出。

但也许你的意思是主线程直接启动线程“新线程”。该线程还可以执行多项操作,包括启动监视器线程并将作业提交给执行程序服务。或者你可能会问为什么这个线程在ExecutorService正在工作时没有退出,但是为什么它在等待invokeAll()方法返回时会退出?即使该方法返回Future的列表,但是它的文档很清楚它阻止,直到提交给它的所有任务都完成,或者发生异常。

答案 1 :(得分:0)

为什么要中断?

ExecutorService.invokeAll()的API中提到了您的任务中断:

  

抛出:

     

InterruptedException - 如果在等待时被中断,在这种情况下,未完成的任务被取消

因此,当您致电p.invokeAll(threads)期间收到中断时,threads中的所有任务都会被取消。

API未指定是否使用mayInterruptIfRunning调用Future.cancel(),但是如果您查看code for AbstractExecutorServiceThreadPoolExecutor继承invokeAll()的实现{ {1}},您可以看到在启用中断的情况下取消了任务:

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException {
    /* ... */
    try {
        /* ... */
    } finally {
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

我认为这比没有中断取消它们更有意义,因为已经有一个中断;这只是“宣传它”。

为什么线程池没有完成?

程序没有退出,并且线程池没有关闭或终止,因为你根本就没有告诉它关闭它。

所以这与以下简化程序没有什么不同:

public static void main(String[] args) throws Throwable {
    final ExecutorService p = Executors.newFixedThreadPool(2);
    p.execute(new Runnable() { public void run() { } });
    Thread.sleep(1000);
    System.out.println(String.format("main say: Executor state: isShutdown: %s, isTerminated: %s",
                             p.isShutdown(),
                             p.isTerminated()));
}

意味着关闭它们时,线程池没有任何特殊的魔法猜测;他们等到你实际告诉他们。 Executors.newFixedThreadPool()州的文档:

  

池中的线程将一直存在,直到明确shutdown

创建线程池时,需要确保最终清理它们。通常这是通过致电shutdown()shutdownNow()。为什么这有必要?因为运行线程在Java垃圾回收的上下文中是特殊的。运行线程是确定哪些对象不会被垃圾收集的起点,并且在它们仍在运行时永远不会被垃圾收集。并且在仍有运行线程的情况下,Java程序永远不会退出(当然,除非您调用System.exit()。)

某些特殊情况,其中线程池可能没有正在运行的线程,因此会被垃圾回收。 API docs for ThreadPoolExecutor解释了这一点:

  

定稿

     

程序中不再引用且没有剩余线程的池将自动关闭。如果您希望确保即使用户忘记调用shutdown()也会回收未引用的池,那么您必须通过设置适当的保持活动时间,使用零核心线程的下限来安排未使用的线程最终死亡/或设置allowCoreThreadTimeOut(boolean)

所以我们可以修改上面的例子,最终像这样退出:

final ThreadPoolExecutor p = new ThreadPoolExecutor(
    0, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

或者这个:

final ThreadPoolExecutor p = new ThreadPoolExecutor(
    2, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
p.allowCoreThreadTimeOut(true);

但是,当你完成线程池时,调用shutdownshutdownNow通常更简洁,而不是依赖于超时。