使用ExecutorService的Java应用程序永远不会关闭

时间:2016-03-17 21:14:16

标签: java multithreading executorservice completable-future

我正在尝试编写一个程序,将多个java工作线程之间的工作分开。问题是,当我从命令行运行它时,它永远不会返回。我没有得到我的提示,最终必须按ctrl-c关闭程序。

我已将其简化为以下简单案例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestExeServ {

    private class ExpensiveTask implements Callable<Integer>{
        private final String msg; 
        public ExpensiveTask(String str){
            this.msg = str;
        }

        @Override
        public Integer call()  {
            System.out.println( "My message was " + msg);
            return 1;
        }
    }

    private void run()
    {
        final ExecutorService exeServ = Executors.newFixedThreadPool(2);
        Future<Integer> result = exeServ.submit(new ExpensiveTask("Hello!") );
        try {
            System.out.println( " Task is done, it returned " + result.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }   

    public static void main(String args[])
    {
        System.out.println( "Start");       

        TestExeServ tes = new TestExeServ();
        tes.run();
        System.out.println( "Done");
    }       
}

该程序的输出是

   djc@djc-linux:/local/mnt/workspace/TestExeServ/bin$ java TestExeServ
   Start
   my message was Hello!
    Task is done, it returned 1
   done

就是这样。它挂在那里。没有提示。如果我删除ExecutorService.submit行,我会得到

   djc@djc-linux:/local/mnt/workspace/TestExeServ/bin$ java TestExeServ
   Start
   done
   djc@djc-linux:/local/mnt/workspace/TestExeServ/bin$

该计划自然结束。

我是否需要在ExecutorService上执行一些我无法正常执行的清理任务?我假设.get()调用加入了线程。那不是这样吗?

1 个答案:

答案 0 :(得分:5)

您必须调用ExecutorService#shutdown()ExecutorService#shutdownNow()来终止执行程序的线程池。否则线程将保持活动状态,从而阻止JVM终止。

来自ExecutorService类Javadoc:

  

可以关闭ExecutorService,这将导致它拒绝新任务。提供了两种不同的方法来关闭ExecutorService。 shutdown()方法将允许先前提交的任务在终止之前执行,而shutdownNow()方法阻止等待任务启动并尝试停止当前正在执行的任务。终止时,执行程序没有正在执行的任务,没有等待执行的任务,也没有新的任务可以提交。

请注意,作为最后的手段,您使用的ThreadPoolExecutor会在收集垃圾时自行关闭 - 其finalize()方法会调用shutdown()。但是,最好不要依赖它并明确关闭执行程序。

正如@mre在评论中说明的那样,我会明确指出 - 您可以安全地在exeServ.shutdown()方法结束时致电run(),因为Future.get()会阻止,所以代码执行是在get()之后你不再需要执行者了。但是,如果这是更复杂的现实场景的简化版本,您可能需要找到安全调用shutdown()的正确位置。