背景
平時自己在使用的ThreadPoolExecutor的時候,提交任務用submit和execute方法用的比較隨意,知道當需要獲取返回結果的時候用submit。但當並不需要結果的時候submit和execute用得比較隨意。在一次使用submit的時候並沒有獲得預期結果,但也沒有異常日誌輸出。在進行一波調試之後,任務在線程中出現異常了,但也並未出現異常拋出的情況。
代碼測試
1、先用execute 查看當出現異常的情況
public class ThreadPoolTest {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(500),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("submit-execute-test");
return thread;
}
}
);
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS");
public static void main(String[] args) {
Random random = new Random();
for (int i = 0; i < 10; i++) {
int j = i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2 + random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (j % 2 != 0) {
throw new RuntimeException("線程錯誤");
}
LinkedBlockingQueue<Runnable> queue = (LinkedBlockingQueue) executor.getQueue();
System.out.println(formatter.format(LocalDateTime.now()) + "::" + Thread.currentThread().getName() + "::" +
"核心線程數 :" + executor.getCorePoolSize() +
"::最大線程數 :" + executor.getMaximumPoolSize() +
"::活動線程數 :" + executor.getActiveCount() +
"::任務完成數" + executor.getCompletedTaskCount() +
"::隊列使用 :" + queue.size() + "::隊列未使用 :" + queue.remainingCapacity() + "::隊列總共大小 :" + (queue.size() + queue.remainingCapacity()));
}
});
}
}
}
從打印中可以看到當execute提交runable的時候異常會輸出。
2、我們將execute 換成submit ,其他都不變,再次查看運行情況
可以看出除了我們在代碼裏面輸出內容並未拋出異常。
原因
查看ThreadPoolExecutor 的submit方法,是將Runable方法進行封裝,再交給Executor去execute,這個方法和ThreadPoolExecutor 的execute方法一樣
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
newTaskFor 方法是創建FutureTask 對象,那麼具體不拋異常應該就在FutureTask的run方法了。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
FutureTask 的run方法,在run方法中對Throwable 異常進行捕獲,錯誤的時候將異常設置到返回結果。執行正常結束,將返回結果set到結果中
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);
}
}
建議
根據上面的分析,如果ThreadPoolExecutor 提交任務的時候如果並不關心返回結果最好直接使用ThreadPoolExecutor.execute() 方法。