多线程最容易犯的错集合

ScheduleService/ScheduledThreadPoolExecutor 没有 try-catch 导致定时任务不执行

现象

定时执行的任务如果没有对执行逻辑 try-catch,后续所有定时任务都会取消,并且线程卡在 AQS 等待唤醒

1
2
3
4
5
6
7
8
9
10
11
12
"starrocks-full-timeout-flush-trigger-6-thd-0" #56 prio=5 os_prio=0 tid=0x00007f2eed8b8800 nid=0x3c1bff waiting on condition [0x00007f2eba8f8000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000003c08d63c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)

原理

ScheduledThreadPool 中的定时任务都是封装成 ScheduledFutureTask,本质是 FutureTask。源码执行逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);
}
}


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

如果执行异常,这个异常只是赋值给 outcome,不会有任何异常打印。

最佳实践

多线程异步处理的时候,都显式使用 try-catch 以及日志打印

submit 和 execute 异常处理不正确导致吞异常

现象

使用 submit,即使配置了 exception handler,仍然没有进行正确的异常处理

原理

execute service 使用 execute 的时候 jvm 才会去调用 uncaughtExceptionHandler,使用 submit 的时候是封装成 FutureTask 的,exception 赋值给 outcome,只有使用 future.get 的时候才可以获取到 exception

1
2
3
4
5
6
7
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}

最佳实践

任何时候都记得在异步线程中使用 try-catch 处理异常