【问题标题】:Java catch/finally blocks skipped completely?Java catch/finally 块完全跳过?
【发布时间】:2012-07-11 17:15:26
【问题描述】:

我们有一个 ThreadPoolExecutor-back 任务执行模块。有些任务显然在应用程序代码中完成了,但是工作代码不知何故跳过了所有的 try/catch/finally 并继续执行下一个任务,导致前一个任务错过了重要的状态报告代码。

就像在这个例子中一样,257832:5 被线程 8 拾取,但这个线程最终只是开始另一个任务。

2012-07-11 15:53:39,389 信息 [pool-1-thread-6]:任务 (258861:5):开始。 2012-07-11 15:53:39,389 信息 [pool-1-thread-6]: 运行的任务 ### 由应用程序逻辑记录 2012-07-11 15:54:18,186 信息 [pool-1-thread-6]:任务 (258868:5):开始。 ###突然又捡了一个! 2012-07-11 15:54:18,186 信息 [pool-1-thread-6]: 运行的任务 ### 由应用程序逻辑记录 2012-07-11 15:54:18,445 信息 [pool-1-thread-6]:任务 (258868:5):从 Task.doWork 返回。 2012-07-11 15:54:18,446 信息 [pool-1-thread-6]:任务 (258868:5):完成。 2012-07-11 15:54:18,487 信息 [pool-1-thread-6]:任务 (258868:5):通知状态结果:200。 2012-07-11 15:54:18,487 信息 [pool-1-thread-6]:任务 (258868:5):已完成

ThreadPoolExecutor 的 Runnable)看起来不错。好像

public void run() {
    log.info(String.format("Task (%s:%s) : Starting.", wfId, taskId));
    try {
        Object result = task.doWork(...); // call the application codes
        log.info(String.format("Task (%s:%s) : returned from Task.doWork.", wfId, taskId));
        status = "DONE";
    } catch (Throwable t) {
        log.error("Error executing task: " + taskId + " - " + className);
    } finally {
        log.info(String.format("Task (%s:%s) : finalizing.", wfId, taskId));
        // notify status to a server
        log.info(String.format("Task (%s:%s) : Finished", wfId, taskId));
    }
}
    // the task framework looks like
    // Use a queue size larger than numThreads, since the ThreadPoolExecutor#getActiveCount() call only returns an approximate count
    this.executor = new ThreadPoolExecutor(numThreads, numThreads, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(numThreads * 2));
    executor.execute(new TaskRunnable(...));

注意:

  1. 我们捕获了终极 Throwable,但在两个任务之间没有记录异常
  2. 没有迹象表明 exit() 被调用或程序重新启动
  3. 第一个跳过了所有的日志行,无论是在调用应用程序代码之后的那个还是在 catch 和 finally 块中的那个,并且跳过了状态报告代码
  4. 这只是随机发生的概率很低;但是由于执行的任务量很大,还是让我们很头疼。

好像线程池执行器只是神奇地驱逐了正在运行的可运行对象(否则它需要一个 InterruptedException,它会被捕获为 Throwable;Java 线程不会以非合作方式停止,除非在关闭/退出期间)和跳过所有块。我检查了 ThreeadPoolExecutor javadoc,没有什么会导致此类事件发生。

会发生什么?

【问题讨论】:

  • 如果它跳过了 所有 日志行,包括 try 块开始之前的行,那么第一个日志调用可能会引发异常,因为该行是不在 try/catch/finally 中,则异常将立即终止 run 方法和线程。
  • 但从日志中可以看出,应用逻辑中的第一行和第一行都被记录了。
  • 什么是 wfId 和 taskId?它们来自哪里,它们是如何设置的?它们在线程之间共享吗?如果是,它们是如何同步的?
  • task.doWork(...) 会导致新任务同步运行吗?

标签: java try-catch finally


【解决方案1】:

finally 块没有出现的日志消息的一个可能解释是在 finally 块执行时引发了异常:

  • 如果此时 log 为空,您将获得 NPE。 (如果log 被声明为final,您可以将其排除为可能的原因。)

  • 根据对象是什么,您可能会在 wfIdtaskId 对象的 toString() 方法中获得未经检查的异常。

  • Logger 对象可能被破坏...


您也有可能因为某种原因查看了错误的源代码。


另一个理论上的可能性是Android平台实现Thread.destroy()来实际做某事,并且在线程上调用了它。如果这个 DEPRECATED 方法是根据原始 javadoc 实现的,你会发现 finally 块没有被执行。也可以从 JVM 外部执行等效的操作。但如果是这样的话,所有的赌注都没有了!!

【讨论】:

  • log/wfId/taskId 在 runnable 的构造函数中初始化,并且在整个代码中都没有修改。 wfId/taskId 是 int/long 类型。
猜你喜欢
  • 2017-09-11
  • 2011-06-01
  • 2015-09-05
  • 1970-01-01
  • 2017-12-19
  • 2011-08-31
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多