转载

【Java基础】线程池中异常丢失现象分析

背景

最近在开发一个证书管理的需求,在Spring容器启动之后需要在线程中异步定时更新SSL证书。于是我使用ScheduledThreadPoolExecutor线程池来定时更新线程。

  1. 在调试的过程中发现有丢失日志的现象,线程执行到某一步之后就没有再执行后面的代码了。定时任务只执行了一次。
  2. 跟踪代码发现代码在执行到了某一步之后,突然消失,没有任何异常抛出的现象。但是分析代码在消失的位置应该出现空指针异常的,那么为什么异常会突然消失呢?

分析源码

首先我们需要对IDEA进行设置,是它能够进入jdk里的代码,如下图。

【Java基础】线程池中异常丢失现象分析

如果我们勾选了Do not step into the classes,调试时,不会进入下面类中,而是直接跳过了。这就是为什么为什么我之前调试过程中发现代码执行到了某一步就突然消失了。因为后面要执行的代码都在图中下面的类中。

其次执行如下代码并点击下一步。

class Solution {  
  public static void main(String[] args) {  
  ScheduledExecutorService scheduledThreadPool = new ScheduledThreadPoolExecutor(1);  
        scheduledThreadPool.scheduleAtFixedRate(() -> {  
  throw new RuntimeException();  
        }, 0, 10, TimeUnit.SECONDS);  
    }  
  
}

抛出异常之后代码进入了FutureTask里的runAndReset()方法里。

protected boolean runAndReset() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return false;
    boolean ran = false;
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                c.call(); // don't set result
                ran = true;
            } catch (Throwable ex) {
                setException(ex);
            }
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    return ran && s == NEW;
}

我们可以从上面的源码中确定抛出的异常被捕获了,并通过setException()方法处理

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

由此我们可以确定抛出的异常被放在了outcome变量里面。

当需要处理线程池中的异常时,我们应该怎么处理呢?

异常是一步一步向上抛出直到被runAndReset里面的try-catch语句块捕获为止。所以如果我们需要在异常中处理异常,那么我们需要在被runAndSet里的try-catch捕获之前进行捕获处理即可。

原文  https://segmentfault.com/a/1190000021396025
正文到此结束
Loading...