線程池的Execute方法和Submit方法有什么區(qū)別?
本文轉(zhuǎn)載自微信公眾號「小姐姐味道」,作者小姐姐養(yǎng)的狗 。轉(zhuǎn)載本文請聯(lián)系小姐姐味道公眾號。
文章內(nèi)容很聚焦,但干貨十足。不注意的話你可能會(huì)落入陷阱。
concurrent包里的ExecutorService,是一個(gè)接口,繼承的是Executor,而Executor里只有一個(gè)方法。
- public interface Executor {
- void execute(Runnable command);
- }
這就是execute方法,接受一個(gè)runnable,然后返回為空。也就是說,它接受任務(wù)之后,就靜悄悄異步去運(yùn)行了。
我們再來看submit方法。區(qū)別就是submit方法,會(huì)返回一個(gè)Future對象。顯然它是比execute方法多了一些內(nèi)容的。
- <T> Future<T> submit(Callable<T> task);
- <T> Future<T> submit(Runnable task, T result);
- Future<?> submit(Runnable task);
問題
我們嘗試運(yùn)行以下代碼:
- ExecutorService service = Executors.newFixedThreadPool(1);
- Runnable r = () -> System.out.println(1 / 0);
- service.submit(r);
- service.shutdown();
程序靜悄悄的什么都沒有輸出,異常沒有,日志也沒有,我們的錯(cuò)誤直接被吞掉了。
把submit方法換成execute方法,可以看到異常能夠正常輸出。為了避免抄襲,我還是輸出一些自定義的堆棧吧。
- Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
- at com.github.xjjdog.pool.AAA.lambda$main$0(AAA.java:13)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
- at java.lang.Thread.run(Thread.java:748)
但它也僅僅是輸出而已,我們無法使用logback之類的日志框架對其進(jìn)行記錄,因?yàn)樗@個(gè)打印動(dòng)作我們是不可控的。
解決方法
首先看下submit 方式的解決方法。通過返回的Future,執(zhí)行它的get方法,即可獲取完成的錯(cuò)誤堆棧。
- ExecutorService service = Executors.newFixedThreadPool(1);
- Runnable r = () -> System.out.println(1 / 0);
- Future f = service.submit(r);
- f.get();
- service.shutdown();
下面是輸出結(jié)果。
- Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
- at java.util.concurrent.FutureTask.report(FutureTask.java:122)
- at java.util.concurrent.FutureTask.get(FutureTask.java:192)
- at com.github.xjjdog.pool.AAA.main(AAA.java:20)
- Caused by: java.lang.ArithmeticException: / by zero
- at com.github.xjjdog.pool.AAA.lambda$main$0(AAA.java:16)
- at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
- at java.util.concurrent.FutureTask.run(FutureTask.java:266)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
- at java.lang.Thread.run(Thread.java:748)
但我們平常情況下,使用Future的時(shí)候并不多,因?yàn)樗鼤?huì)阻塞我們的請求。
你可能懷疑不調(diào)用get,我們的代碼沒有運(yùn)行,其實(shí)不是的。把runnable改成如下代碼,不調(diào)用get方法,發(fā)現(xiàn)程序只輸出了一個(gè)a。
- Runnable r = () -> {
- System.out.println("a");
- System.out.println(1 / 0);
- };
真是讓人惱火啊,想要拋出異常,還是使用execute方便一些。
但我們上面說到,execute的方式,錯(cuò)誤也是無法捕捉。其實(shí)我們可以曲線救國的繞一下去解決。解決方式就是使用ThreadFactory,實(shí)現(xiàn)它的UncaughtExceptionHandler。具體代碼如下:
- ThreadFactory factory = r->{
- Thread thread = Executors.defaultThreadFactory().newThread(r);
- thread.setUncaughtExceptionHandler( (t,e) -> {
- System.out.println(t + "" + e);
- e.printStackTrace();//example
- });
- return thread ;
- };
- ExecutorService service = Executors.newFixedThreadPool(1,factory);
- Runnable r = () -> {
- System.out.println("a");
- System.out.println(1 / 0);
- };
- service.execute(r);
- service.shutdown();
運(yùn)行之后,能夠看到我們的自定義異常捕獲。
- a
- Thread[pool-1-thread-1,5,main]java.lang.ArithmeticException: / by zero
EndJava線程池對于異常處理的這些默認(rèn)行為,以及差別,我是特別抵觸的??梢哉f兩種默認(rèn)行為都很low,我們還需要處理很多動(dòng)作,才能捕捉到合適的異常。
多線程編程本來就難,又搞出這么兩套東西來。找個(gè)日志吧,習(xí)慣性的往項(xiàng)目的error日志里去找,并沒有。真是苦了開發(fā)同學(xué)。
作者簡介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個(gè)人微信xjjdog0,歡迎添加好友,進(jìn)一步交流。