線程池的Execute方法和Submit方法有什么區別?
本文轉載自微信公眾號「小姐姐味道」,作者小姐姐養的狗 。轉載本文請聯系小姐姐味道公眾號。
文章內容很聚焦,但干貨十足。不注意的話你可能會落入陷阱。
concurrent包里的ExecutorService,是一個接口,繼承的是Executor,而Executor里只有一個方法。
- public interface Executor {
- void execute(Runnable command);
- }
這就是execute方法,接受一個runnable,然后返回為空。也就是說,它接受任務之后,就靜悄悄異步去運行了。
我們再來看submit方法。區別就是submit方法,會返回一個Future對象。顯然它是比execute方法多了一些內容的。
- <T> Future<T> submit(Callable<T> task);
- <T> Future<T> submit(Runnable task, T result);
- Future<?> submit(Runnable task);
問題
我們嘗試運行以下代碼:
- ExecutorService service = Executors.newFixedThreadPool(1);
- Runnable r = () -> System.out.println(1 / 0);
- service.submit(r);
- service.shutdown();
程序靜悄悄的什么都沒有輸出,異常沒有,日志也沒有,我們的錯誤直接被吞掉了。
把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之類的日志框架對其進行記錄,因為它這個打印動作我們是不可控的。
解決方法
首先看下submit 方式的解決方法。通過返回的Future,執行它的get方法,即可獲取完成的錯誤堆棧。
- ExecutorService service = Executors.newFixedThreadPool(1);
- Runnable r = () -> System.out.println(1 / 0);
- Future f = service.submit(r);
- f.get();
- service.shutdown();
下面是輸出結果。
- 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的時候并不多,因為它會阻塞我們的請求。
你可能懷疑不調用get,我們的代碼沒有運行,其實不是的。把runnable改成如下代碼,不調用get方法,發現程序只輸出了一個a。
- Runnable r = () -> {
- System.out.println("a");
- System.out.println(1 / 0);
- };
真是讓人惱火啊,想要拋出異常,還是使用execute方便一些。
但我們上面說到,execute的方式,錯誤也是無法捕捉。其實我們可以曲線救國的繞一下去解決。解決方式就是使用ThreadFactory,實現它的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();
運行之后,能夠看到我們的自定義異常捕獲。
- a
- Thread[pool-1-thread-1,5,main]java.lang.ArithmeticException: / by zero
EndJava線程池對于異常處理的這些默認行為,以及差別,我是特別抵觸的。可以說兩種默認行為都很low,我們還需要處理很多動作,才能捕捉到合適的異常。
多線程編程本來就難,又搞出這么兩套東西來。找個日志吧,習慣性的往項目的error日志里去找,并沒有。真是苦了開發同學。
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高并發世界,給你不一樣的味道。我的個人微信xjjdog0,歡迎添加好友,進一步交流。