java.util.Timer 之后释放资源的最佳方式是啥?

Posted

技术标签:

【中文标题】java.util.Timer 之后释放资源的最佳方式是啥?【英文标题】:What's the best way to release resources after java.util.Timer?java.util.Timer 之后释放资源的最佳方式是什么? 【发布时间】:2021-09-28 23:34:05 【问题描述】:

我有一个 AutoCloseable,它的 close() 方法被过早地调用。 AutoCloseable 是下面的 ProcessQueues。我不希望在当前调用 close() 方法时调用它。我正在考虑删除“实现 AutoCloseable”来实现这一点。但是我怎么知道什么时候调用 ProcessQueues.close()?

 public class ProcessQueues implements AutoCloseable 
    private ArrayList<MessageQueue> queueObjects    = new ArrayList<MessageQueue>();

    public ProcessQueues() 
        queueObjects.add(new FFE_DPVALID_TO_SSP_EXCEPTION());
        queueObjects.add(new FFE_DPVALID_TO_SSP_ESBEXCEPTION());
        ...
    
    
    private void scheduleProcessRuns() 
        try 
            for (MessageQueue obj : queueObjects) 
                monitorTimer.schedule(obj, new Date(), 1); // NOT THE ACTUAL ARGUMENTS
            
        
        catch (Exception ex) 
           // NOT THE ACTUAL EXCEPTION HANDLER
        
    

    public static void main(String[] args)    
      try (ProcessQueues pq = new ProcessQueues()) 
        pq.scheduleProcessRuns();
       catch (Exception e) 
         // NOT THE ACTUAL EXCEPTION HANDLER
      
    

    @Override
    public void close() throws Exception 
      for (MessageQueue queue : queueObjects) 
        queue.close();
      
    

我希望 ProcessQueues.close() 被调用,但直到所有 Timer 对象的任务执行线程终止。正如所写,一旦任务被调度,ProcessQueues.close() 就会被调用。我可以通过从 ProcessQueues 类中删除“实现 AutoCloseable”(并删除 @Override 注释)轻松解决这个问题。但是我必须自己调用 ProcessQueues.close() 。如何知道所有 Timer 对象的任务执行线程何时终止?那时我想调用 ProcessQueues.close()。

请注意,MessageQueue 并未在 try-with-resources 块的资源规范标头中实例化,因此尽管 MessageQueue 也实现了 AutoCloseable,但此处并未使用该功能。我明确地调用 MessageQueue.close()。我在 MessageQueue.close() 中释放资源。过早释放这些资源会导致任务执行线程无法完成其任务。

我正在考虑在重写代码后显式调用 ProcessQueues.close() 以防止自动资源释放,但我还是不知道如何为该显式调用找到合适的时间。

我考虑过覆盖 ProcessQueues.finalize(),但“Java:如何编程”,第 11 版不建议这样做。 “你永远不应该使用 finalize 方法,因为它会导致很多问题,并且不确定它是否会在程序终止之前被调用......现在对于任何使用系统资源的类来说,它被认为是更好的做法......提供一个当程序不再需要资源时,程序员可以调用该方法来释放资源。”我有这样的方法。它是 ProcessQueues.close()。但是我应该什么时候调用呢?

【问题讨论】:

close 将在try-catch 退出时被调用,这可能不是最佳选择 你为什么不使用几年前取代 Timer 类的 Executors 框架? catch (Exception e) 不要默默吞下异常 你明白monitorTimer.schedule(obj, new Date(), 1) 安排一个任务每毫秒重复运行一次吗?为什么这么快,每毫秒?如果安排无休止地重复,您的Timer“已终止”意味着什么?重复任务将无限期地重复。还是我在这里遗漏了什么? 当编写Closeables 并在一个close() 方法中关闭多个资源时,您应该在循环中添加一个try-catch,这样如果一个资源失败,您就不会让其他资源保持打开状态关闭。记住发生的第一个异常,通过.addSuppressed 添加所有后续异常。如果发生一个异常,则在关闭所有资源后抛出第一个异常(所有后续都通过addSuppressed 添加)。 【参考方案1】:

这里有相互冲突的生命周期问题。

您有 Timer,其生命周期 100% 由您控制。你开始它,你停止它,就是这样。但是您无法直接自省 Timer 管理的线程的状态。因此,例如,您不能询问它当前是否有任何东西在运行。

然后你有你的MessageQueue,它由Timer 调用。这是您感兴趣的生命周期。您希望等待所有MessageQueues“完成”,等待各种完成的值。但是,由于队列不断被重新安排(考虑到您正在使用的Timer.schedule 方法),它们永远不会“完成”。他们处理他们的内容,然后再次运行。

那么,如何知道“完成”何时意味着“完成”?

这取决于MessageQueue?还是取决于ProcessQueues?谁在这里指挥?

注意,没有任何东西会取消Timer。它只是不停地运行。

那么,如何知道MessageQueue 何时可以关闭?

如果MessageQueue 是这里的真正驱动程序,那么您应该向MessageQueue 添加生命周期方法,ProcessQueues 可以监控以了解何时关闭事物。例如,您可以为列表中的许多MessageQueues 创建一个CountDownLatch 集,然后订阅MessageQueue 上的新生命周期方法,它在完成时调用该方法。然后回调方法可以递减CountDownLatchProcessQueues.close 方法只是在关闭所有内容之前等待锁存器倒计时。

public class ProcessQueues implements AutoCloseable, MessageQueueListener 

    private ArrayList<MessageQueue> queueObjects = new ArrayList<MessageQueue>();
    CountDownLatch latch;

    public ProcessQueues() 
        queueObjects.add(new FFE_DPVALID_TO_SSP_EXCEPTION());
        queueObjects.add(new FFE_DPVALID_TO_SSP_ESBEXCEPTION());
        ... 
        queueObjects.forEach((mq) -> 
            mq.setListener(this);
        );
        latch = new CountDownLatch(queueObjects.size());
    

    private void scheduleProcessRuns() 
        try 
            for (MessageQueue obj : queueObjects) 
                monitorTimer.schedule(obj, new Date(), 1); // NOT THE ACTUAL ARGUMENTS
            
         catch (Exception ex) 
            // NOT THE ACTUAL EXCEPTION HANDLER
        
    

    public static void main(String[] args) 
        try (ProcessQueues pq = new ProcessQueues()) 
            pq.scheduleProcessRuns();
         catch (Exception e) 
            // NOT THE ACTUAL EXCEPTION HANDLER
        
    

    @Override
    public void close() throws Exception 
        latch.await();
        for (MessageQueue queue : queueObjects) 
            queue.close();
        
        monitorTimer.cancel();
    

    @Override
    public void messageQueueDone() 
        latch.countDown();
    


public interface MessageQueueListener     
    public void messageQueueDone();


public class MessageQueue extends TimerTask 
    MessageQueueListener listener;
    
    public void setListener(MessageQueueListener listener) 
        this.listener = listener;
    
    
    private boolean isMessageQueueReallyDone 
        ...
    
    public void run() 
        ...
        if (isMessageQueueReallyDone() && listener != null) 
            listener.messageQueueDone();
        
    

请注意,这意味着您的 try-with-resource 块将阻止等待所有 MessageQueues,如果这是您想要的,那么您就可以开始了。

它还粗鲁地假设您的 MessageQueue.run() 知道何时关闭,这可以追溯到“谁在这里控制”的事情。

【讨论】:

"如果 MessageQueue 是真正的驱动程序,那么"您的 CountDownLatch 示例非常好,但 MessageQueue 不是真正的驱动程序。 ProcessQueues 想成为驱动程序,但是当它委托给 Timer 时它失去了控制。 消息队列不知道何时完成。它甚至不知道 Timer 导致它重复运行,但 ProcessQueues 知道无穷无尽。 @AlanFeldstein MessageQueues 可以知道它们何时空闲(即“未运行”)。 ProcessQueues 可以取消 Timer,然后告诉 MessageQueues 他们已经完成,然后等待他们的“I'm done”闩锁。当 ProcessQueues 告诉他们他们已经完成时,如果他们空闲,他们可以立即设置他们的锁存器。否则,它会设置一个运行方法查看的标志(AtomicBoolean),当它即将退出时,它可以设置完成锁存器,“知道”它不会被计时器再次调用。处理同步会有一些乐趣,但这是正常的。【参考方案2】:

我可以终止Timer,但让它永久运行是有意的。问题是考虑到当其他事物终止 Timer 并且不再需要 MessageQueue 对象时会发生什么。那时我想打电话给ProcessQueues.close()

如果我要使用 Executor 框架,而不是 Timer,那么我可以使用 ExecutorService.awaitTermination(long timeout, TimeUnit unit)

TimerTask 是一个Runnable,而MessageQueue 已经是一个TimerTask,所以MessageQueue 不需要改变。

'ExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)' 实际上会永远等待终止。

  public static void main(String[] args)    
  try (ProcessQueues pq = new ProcessQueues()) 
    pq.scheduleProcessRuns();

    // Don't take this literally.
    ExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);

   catch (Exception e) 
     // NOT THE ACTUAL EXCEPTION HANDLER
  

当然,awaitTermination 不是静态方法,所以我必须有一个 ExecutorService,但你明白了。

终止后,AutoCloseable 功能被利用,ProcessQueues.close() 被隐式调用。

剩下的就是使用 Executor 框架启动线程以对每个TimerTask 进行永久重复调用。这个问题的答案是ScheduledExecutorService

我认为这会奏效。

【讨论】:

以上是关于java.util.Timer 之后释放资源的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何停止在 java.util.Timer 类中安排的任务

java.util.Timer简介

自己定义定时器(Timer)

如何在没有 java.util.Timer 的情况下实现延迟的未来?

java中如何是方法延迟执行?

C# using()的本质