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
“已终止”意味着什么?重复任务将无限期地重复。还是我在这里遗漏了什么?
当编写Closeable
s 并在一个close()
方法中关闭多个资源时,您应该在循环中添加一个try-catch,这样如果一个资源失败,您就不会让其他资源保持打开状态关闭。记住发生的第一个异常,通过.addSuppressed
添加所有后续异常。如果发生一个异常,则在关闭所有资源后抛出第一个异常(所有后续都通过addSuppressed
添加)。
【参考方案1】:
这里有相互冲突的生命周期问题。
您有 Timer
,其生命周期 100% 由您控制。你开始它,你停止它,就是这样。但是您无法直接自省 Timer 管理的线程的状态。因此,例如,您不能询问它当前是否有任何东西在运行。
然后你有你的MessageQueue
,它由Timer
调用。这是您感兴趣的生命周期。您希望等待所有MessageQueues
“完成”,等待各种完成的值。但是,由于队列不断被重新安排(考虑到您正在使用的Timer.schedule
方法),它们永远不会“完成”。他们处理他们的内容,然后再次运行。
那么,如何知道“完成”何时意味着“完成”?
这取决于MessageQueue
?还是取决于ProcessQueues
?谁在这里指挥?
注意,没有任何东西会取消Timer
。它只是不停地运行。
那么,如何知道MessageQueue
何时可以关闭?
如果MessageQueue
是这里的真正驱动程序,那么您应该向MessageQueue
添加生命周期方法,ProcessQueues
可以监控以了解何时关闭事物。例如,您可以为列表中的许多MessageQueues
创建一个CountDownLatch
集,然后订阅MessageQueue
上的新生命周期方法,它在完成时调用该方法。然后回调方法可以递减CountDownLatch
,ProcessQueues.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 之后释放资源的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章