java.util.concurrent.ExecutorService.awaitTermination()未按预期工作

ctzwtxfj  于 2023-06-04  发布在  Java
关注(0)|答案(1)|浏览(355)

我有个问题pool.awaitTermination()后面的代码不执行。

ExecutorService pool = Executors.newFixedThreadPool(6);

        for (HierarchyItem rootItem : rootObjectItems) {
            pool.submit(() -> {
                final Thread currentThread = Thread.currentThread();
                currentThread.setName("Processing " + rootItem.getName());
                try {
                    addressObjectProcessor.handle(rootItem);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }

        pool.shutdown();
        try {
            boolean poolTermination = pool.awaitTermination(5, TimeUnit.HOURS);
            System.out.println(poolTermination);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /// code after pool.awaitTermination. It's not executed
         . . . .

我在日志中看到,所有任务都完全完成了,但是pool.awaitTermination(5, TimeUnit.HOURS);之后的代码没有执行。当rootObjectItems大小为90时出现此问题。当rootObjectItems大小为15时,一切正常,但当我增加rootObjectItems时,看起来主线程休眠或停止运行此方法,即使在超时后也没有任何React。每个任务处理需要5到60分钟。
pool.awaitTermination结果永远不会打印到日志中,并且永远不会抛出InterruptedException。
我是新来的兼职,请帮我解决这个问题。非常感谢提前!

whlutmcx

whlutmcx1#

捕获awaitTermination的结果

我建议您尽可能简单地重新创建这个场景来进行一些调试。添加片段以更接近您当前的场景。
最重要的是,👉跟踪您的awaitTermination发生了什么。您的代码将忽略其返回值boolean。引用Javadoc:
返回:true(如果此执行器已终止)和false(如果在终止前超时)
下面是一些简化的代码。
顺便说一句,我建议 * 不要 * 使用术语 * 池 * 关于您的遗嘱执行人服务。执行器服务可能涉及也可能不涉及线程的后备池。该服务的目的是让我们忽略管理线程和线程池的细节。

System.out.println ( "Demo begins. " + Instant.now ( ) );
ExecutorService executorService = Executors.newFixedThreadPool ( 6 );

List < Integer > numbers = IntStream.rangeClosed ( 1 , 100 ).boxed ( ).toList ( );
for ( Integer number : numbers )
{
    executorService.submit ( ( ) ->
    {
        Thread.currentThread ( ).setName ( "Processing " + number );
        try
        {
            Thread.sleep ( Duration.ofMinutes ( 60 ) );
        }
        catch ( InterruptedException e )
        {
            System.out.println ( "Sleep interrupted on thread: " + Thread.currentThread ( ).getName ( ) );
            return;
        }
        System.out.println ( "Finished the run for # " + number + " on thread: " + Thread.currentThread ( ).getName ( ) );
    } );
}

executorService.shutdown ( );
try
{
    System.out.println ( "Awaiting termination. " + Instant.now ( ) );
    boolean awaitTerminatedBeforeTimeLimit = executorService.awaitTermination ( 5 , TimeUnit.MINUTES );
    System.out.println ( "awaitTerminatedBeforeTimeLimit = " + awaitTerminatedBeforeTimeLimit );
}
catch ( InterruptedException e ) { e.printStackTrace ( ); }
System.out.println ( "Demo done. " + Instant.now ( ) );

运行时:

Demo begins. 2023-05-30T23:20:47.255904Z
Awaiting termination. 2023-05-30T23:20:47.263300Z
awaitTerminatedBeforeTimeLimit = false
Demo done. 2023-05-30T23:25:47.289905Z

注意awaitTerminatedBeforeTimeLimitfalse。这意味着完成所有任务所花费的时间比我们传递给awaitTermination的指定时间限制所允许的时间要长。

不当忽略关闭executor服务

另外,非常重要的是,请注意,即使我们的应用程序应该已经结束,我们的执行器服务的后台线程池仍然在运行。

  • 您可以在部署中看出这一点,因为Java进程仍将意外地运行。
  • 在IntelliJ等IDE中,您将看到应用程序执行的 Run 状态尚未停止。在IntelliJ中,显示为红色 * 停止 * 按钮的圆形方形图标仍将处于活动状态,仍然是红色,这意味着应用程序尚未完全结束。

为了避免这种僵尸🧟‍♂️线程池,完全关闭你的executor服务。
要正确关闭executor服务,请参阅ExecutorService接口的Javadoc中提供的样板代码。查找名为shutdownAndAwaitTermination的方法。下面是我对这段代码稍作修改的版本。

private void shutdownAndAwaitTermination ( final ExecutorService executorService , final Duration waitForTermination )
{
    executorService.shutdown ( ); // Disallow any more task submissions to this executor service.
    try
    {
        // Wait a while for existing tasks to terminate.
        if ( ! executorService.awaitTermination ( waitForTermination.toMillis ( ) , TimeUnit.MILLISECONDS ) )
        {
            executorService.shutdownNow ( ); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled.
            if ( ! executorService.awaitTermination ( 60 , TimeUnit.SECONDS ) )
                System.err.println ( "Executor service did not terminate. " + Instant.now ( ) );
        }
    }
    catch ( InterruptedException ex )
    {
        // (Re-)Cancel if current thread also interrupted
        executorService.shutdownNow ( );
        // Preserve interrupt status
        Thread.currentThread ( ).interrupt ( );
    }
}

让我们修改我们的代码来调用该方法,以便正常关闭执行器服务。

System.out.println ( "Demo begins. " + Instant.now ( ) );
ExecutorService executorService = Executors.newFixedThreadPool ( 6 );

List < Integer > numbers = IntStream.rangeClosed ( 1 , 100 ).boxed ( ).toList ( );
for ( Integer number : numbers )
{
    executorService.submit ( ( ) ->
    {
        Thread.currentThread ( ).setName ( "Processing " + number );
        try
        {
            Thread.sleep ( Duration.ofMinutes ( 60 ) );
        }
        catch ( InterruptedException e )
        {
            System.out.println ( "Sleep interrupted on thread: " + Thread.currentThread ( ).getName ( ) );
            return;
        }
        System.out.println ( "Finished the run for # " + number + " on thread: " + Thread.currentThread ( ).getName ( ) );
    } );
}

this.shutdownAndAwaitTermination ( executorService , Duration.ofMinutes ( 5 ) );
System.out.println ( "Demo done. " + Instant.now ( ) );

运行时:

Demo begins. 2023-05-31T05:46:17.163745Z
Sleep interrupted on thread: Processing 3
Sleep interrupted on thread: Processing 1
Sleep interrupted on thread: Processing 4
Sleep interrupted on thread: Processing 5
Sleep interrupted on thread: Processing 6
Sleep interrupted on thread: Processing 2
Demo done. 2023-05-31T05:51:17.182036Z

我们也有同样的情况,我们的运行任务需要的时间比我们允许的要多。但是现在,请注意(a)我们正在执行的任务被成功中断,(b)应用程序已经真正结束。一定要编写自己的任务,以便它们正确地查找中断标志。
提示:如果你确信你的任务会在一天内完成,在现代Java中,你可以使用try-with-resources语法来利用ExecutorService现在是AutoCloseable的优势。

相关问题