【问题标题】:How to keep thread waiting in ScheduledExecutorService that has been shutdown如何在已关闭的 ScheduledExecutorService 中保持线程等待
【发布时间】:2012-05-24 12:06:26
【问题描述】:
public ScheduledFuture<?> executeTaskWithDelay(String name, 
      final Runnable runnable, Period delay, boolean isDaemon) {
  ScheduledExecutorService executorService = 
        Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory(
          name, isDaemon));
  ScheduledFuture<?> future =  executorService.schedule(runnable, 
        delay.toStandardDuration().getMillis(), TimeUnit.MILLISECONDS);
  executorService.shutdown(); 
  return future;
}

当我分析应用程序时,我注意到此方法创建的计划线程在执行之前始终处于“正在运行”而不是“等待”状态。如果我删除 executorService.shutdown() 它会做我想做的事(即线程保持等待状态,直到它们运行的​​时候)。但是,如果没有 executorService.shutdown(),非守护线程在执行后永远不会被垃圾回收。有没有办法可以确保线程在执行前始终处于等待状态?或者我可以为这种方法使用什么其他替代品来确保:

  • 我可以将前缀附加到在执行程序服务中运行的线程的名称(这实际上是 DefaultThreadFactory 实现所做的)
  • 非守护线程在执行后获得 GC。
  • 创建的线程一直处于等待状态,直到它们运行。

【问题讨论】:

    标签: java multithreading executorservice


    【解决方案1】:

    线程没有等待,因为你没有调用 future.get() 方法。 我做了一个单元测试来证明这一点。

    测试#1(不调用 future.get() 方法):

    @Test
    public void testSchedule() throws InterruptedException, ExecutionException {
    
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        System.out.println(new Date());
        ScheduledFuture<?> future =  executorService.schedule(new Runnable() {
    
            public void run() {
                System.out.println(Thread.currentThread().getId() + " - " + Thread.currentThread().getName() + " - Executing thread...");                
            }
    
        }, 5, TimeUnit.SECONDS);
    
        //System.out.println("future : " + future.get());
    
        executorService.shutdown();
        System.out.println(new Date());
    }
    

    输出是:

    Thu May 24 10:11:14 BRT 2012
    Thu May 24 10:11:14 BRT 2012
    

    测试#2(调用future.get()方法):

    @Test
    public void testSchedule() throws InterruptedException, ExecutionException {
    
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        System.out.println(new Date());
        ScheduledFuture<?> future =  executorService.schedule(new Runnable() {
    
            public void run() {
                System.out.println(Thread.currentThread().getId() + " - " + Thread.currentThread().getName() + " - Executing thread...");                
            }
    
        }, 5, TimeUnit.SECONDS);
    
        System.out.println("future : " + future.get());
    
        executorService.shutdown();
        System.out.println(new Date());
    }
    

    输出是:

    Thu May 24 10:12:48 BRT 2012
    8 - pool-1-thread-1 - Executing thread...
    future : null
    Thu May 24 10:12:53 BRT 2012
    

    希望对你有帮助!

    【讨论】:

    • 除非我看错了OP,否则我认为他指的不是提交线程(等待任务完成的线程)。我相信他指的是 ExecutorService 的持续运行的线程。请参阅 Marko Topolnik 的线程转储以观察差异。
    【解决方案2】:

    您看到的是 ScheduledExecutor 委托给普通的 ThreadPoolExecutor 以实现关闭功能。关闭 TPE 时,它的所有线程都会在后备工作队列上旋转,直到它为空。好吧,ScheduledThreadPool 使用了一个可能不为空的 DelayedQueue,但如果你轮询它,你会得到一个空值,因为任务还没有准备好调度。它会不停地旋转直到它准备好

    你唯一能做的就是shutdownNow并执行以其他方式返回的任务。

    另外有人告诉我这实际上已在 Java 7 中修复

    【讨论】:

    • +1 因为前两句话帮助我找到了解决方案。
    【解决方案3】:

    想出了一个解决方案:它是 shutdown() 将所有待处理任务线程的状态从“等待”更改为“运行”。我现在不是立即调用shutdown(),而是使用executorService 来安排调用它自己的shutdown()。这可确保待处理的任务尽可能长时间地保持在等待状态,从而节省 CPU 资源。

    public ScheduledFuture<?> executeTaskWithDelay(String name, 
          final Runnable runnable, Period delay, boolean isDaemon) {
      final ScheduledExecutorService executorService =  
            Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory(
          name, isDaemon));
      ScheduledFuture<?> future =  executorService.schedule(runnable, 
            delay.toStandardDuration().getMillis(), TimeUnit.MILLISECONDS);
    
      executorService.schedule(new Runnable() {
          @Override
          public void run() {
            executorService.shutdown();
          }}, delay.toStandardDuration().getMillis(), TimeUnit.MILLISECONDS);
    
      return future;
    }
    

    【讨论】:

    • +1 干得好。我想建议将此作为一种解决方案,但只有在将一项任务提交到计划时才会起作用(没有其他类型的控制,如执行计数),所以我选择了退出。
    【解决方案4】:

    运行您的代码。这是来自 jvisualvm 中的线程转储(我将线程命名为“bla”):

    "bla" prio=5 tid=7fa2bc16a000 nid=0x10bd53000 runnable [10bd52000]
      java.lang.Thread.State: RUNNABLE
      at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:950)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
      at java.lang.Thread.run(Thread.java:680)
    
      Locked ownable synchronizers:
        - None
    

    现在,这是来自 ThreadPoolExecutor.java:950 的 grepcode:

    944         // It is possible (but unlikely) for a thread to have been
    945         // added to workers, but not yet started, during transition to
    946         // STOP, which could result in a rare missed interrupt,
    947         // because Thread.interrupt is not guaranteed to have any effect
    948         // on a non-yet-started Thread (see Thread#interrupt).
    949         if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())
    950             t.interrupt();
    

    这使我得出结论,问题出在您的测试代码中——您关闭执行程序的速度太快,线程发现自己处于奇怪的状态。这不应该是生产代码中的问题。

    【讨论】:

    • 如果他在等待调度任何任务时关闭执行器,这将是生产中的问题。
    • @JohnVint 只有在线程在其start 方法执行后立即进入STOP 状态的极少数情况下才会执行此特定代码行(如注释所述)(即第 943 行,此处未显示)。您可能指的是一般问题,而不是具体问题。
    • Topolink 只有当ExecutorService为shutdownNow()时才会设置STOP状态shutdown()SHUTDOWN时Executor的状态
    猜你喜欢
    • 1970-01-01
    • 2010-11-23
    • 2018-04-12
    • 1970-01-01
    • 2023-03-08
    • 2015-02-15
    • 2012-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多