我們知道Java 執行緒池提交任務有兩種:
1,submit 會拋出例外,會有例外日志
2,execute 沒有拋出例外,需要執行回傳值Future的get方法觸發拋出例外
execute在執行中沒有做任何包裝,在執行緒池原始碼中會執行到runWorker方法,這個方法中可以看到會把例外拋出,我們也可以通過覆寫afterExecute方法對例外進行自定義操作,這也就是execute會有例外資訊列印的原因,
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
submit在提交程序中會把任務包裝成FutureTask,FutureTask的run方法:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<v> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} 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
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
看代碼就知道,它不會直接把在執行任務的時候把例外直接拋出來,而是執行了 setException(ex);方法把例外存了起來,等到get方法請求時觸發例外,
話要從前幾天一個問題講起,我們應用使用Spring Cloud,我對應用使用的執行緒池進行統一管理,研發在使用execute方法提交任務后,沒有看到執行任務的代碼,也沒有任何報錯資訊,
根據前面的分析,execute有例外是會拋出例外也是有日志資訊的,我除錯了一下例外情況,居然沒有例外拋出,發現我們的執行緒池都被TraceableExecutorService代理了,打開這個類,我就驚呆了,execute方法被篡改成submit提交了,
Spring Cloud 里會使用spring-cloud-sleuth來做日志鏈路跟蹤,一種常見的場景就是在應用中執行緒池使用的執行緒,如何把跟蹤的用的traceId 傳遞過去,Spring的做法是把所有ExecutorService型別的Bean都用 TraceableExecutorService 進行代理,這個代理類生產代碼實作在ExecutorBeanPostProcessor里的postProcessAfterInitialization方法中,具體代碼:
if (bean instanceof ExecutorService
&& !(bean instanceof TraceableExecutorService)) {
if (isProxyNeeded(beanName)) {
return wrapExecutorService(bean);
}
else {
log.info("Not instrumenting bean " + beanName);
}
}
注意,我使用的是Spring Cloud 的版本是Greenwich.SR1,它依賴的spring-cloud-starter-sleuth是2.1.1.RELEASE而這個版本的TraceableExecutorService實作了execute代理方法,把一個execute篡改成submit提交任務,帶來的后果就是預期的例外拋出卻沒有拋出,
public void execute(Runnable command) {
this.delegate.submit(ContextUtil.isContextInCreation(this.beanFactory) ? command
: new TraceRunnable(tracing(), spanNamer(), command, this.spanName));
}
這個是bug產生時的改動:
https://github.com/spring-cloud/spring-cloud-sleuth/commit/17d8165c5e1433cf7b2943ade53f06bdd12a86de#diff-7b796c619250cf07f1d1f80d53c9e32d140e268fdfcea3bee20460fc731c0105

這個是修復bug的描述:
https://github.com/spring-cloud/spring-cloud-sleuth/commit/e2e1cdb336ec89b26fcd22116510c2d5eee6924e

在我看來這是個低級錯誤,復制粘貼代碼出的錯,帶來的影響雖然不大,但在一些場景沒有例外資訊其實是很致命的,甚至會導致一個bug隱藏很久或者一個bug要查很久,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/303708.html
標籤:Java
上一篇:如何開始第一個開源專案?
