Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
問:在 try catch 中開啓新的線程,能捕獲線程裏面的異常嗎?java
例如:git
try { new Thread(() -> System.out.println(1 / 0)).start(); //Runnable的run()方法拋出了 unchecked exception //new Thread(() -> throw new RuntimeException("拋出了 unchecked exception")).start(); } catch (Exception e) { e.printStackTrace(); System.out.println("這裏能執行到嗎?"); //不能夠 }
上面是捕獲不到異常的,而若是改成下面這種形式,則能夠捕獲到異常:github
new Thread(() -> { try { System.out.println(1 / 0); //Runnable的run()方法並無拋出異常,而是本身捕獲了異常 } catch (Exception e) { e.printStackTrace(); System.out.println("這裏能執行到嗎?"); //能夠 } }).start();
其實使用 try catch 捕獲異常時有一個規範,那就是儘可能用 try catch 包住最少的代碼,有些同窗一上來就用 try catch 把整個方法的邏輯包住,這樣很是不合適,好比就會致使上述 try catch 失效。面試
在java多線程程序中,全部線程都不容許拋出checked exception
,也就是說各個線程的checked exception
必須由本身捕獲
。這一點是經過java.lang.Runnable.run()
方法聲明進行的約束,由於此方法聲明上沒有throws
部分。微信
可是線程依然有可能拋出一些運行時的異常(即unchecked exception
,RuntimeException),當此類異常跑拋出時,此線程就會終結
,而對於其餘線程徹底不受影響,且徹底感知不到某個線程拋出的異常。多線程
JVM的這種設計源自於這樣一種理念:線程是獨立執行的代碼片段
,線程的問題應該由線程本身來解決,而不要委託到外部。less
在Java中,線程方法的異常(不管是checked仍是unchecked exception),都應該在線程代碼邊界以內(run方法內)進行try catch並處理掉。換句話說,咱們不能捕獲從線程中逃逸的異常。dom
查看 Thread 的源碼能夠幫忙分析當線程出現未捕獲異常時的處理邏輯。測試
首先看Thread.dispatchUncaughtException()
方法:this
//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); }
這個方法僅僅被 JVM 調用,用來將 uncaught exception 分發到 handler 去處理,這個 handler 是哪來的呢?
public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; }
Returns the handler invoked when this thread abruptly terminates due to an uncaught exception.
返回此線程因爲未捕獲的異常而忽然終止時調用的handler。
If this thread has not had an uncaught exception handler explicitly set then this thread's ThreadGroup object is returned, unless this thread has terminated, in which case null is returned.
若是此線程沒有顯式設置未捕獲的異常 handler,則返回此線程的 ThreadGroup 對象,除非此線程已終止,在這種狀況下返回 null。
這裏的uncaughtExceptionHandler
只有一個地方初始化:
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;// null unless explicitly set public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { checkAccess(); uncaughtExceptionHandler = eh; }
若是返回的是 ThreadGroup 的話,默認會一直找到頂層 ThreadGroup(相似雙親委派模型),而後會找 Thread 類共用的 defaultUncaughtExceptionHandler
,若是存在則調用,若是不存在,則打印線程名字和異常:
public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { //不是ThreadDeath System.err.print("Exception in thread \"" + t.getName() + "\" "); //打印線程名字 e.printStackTrace(System.err); //打印異常 } } }
而defaultUncaughtExceptionHandler
也只有一個地方初始化:
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;// null unless explicitly set public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { //檢查權限 defaultUncaughtExceptionHandler = eh; }
也就是說,當一個線程中有未捕獲的異常時,JVM 會經過調用 UncaughtExceptionHandler 的 uncaughtException 方法處理異常,若是沒有設置,則會直接打印線程名字和異常。
問:Java異常捕獲機制try...catch...finally
塊中的finally
語句是否是必定會被執行?
答:不必定,至少有兩種狀況下finally語句是不會被執行的:
System.exit(0)
這樣的語句,System.exit(0)
是終止Java虛擬機JVM的,連JVM都中止了,全部都結束了,固然finally語句也不會被執行到。finally語句是在try的return語句執行以後,return返回以前
執行的
測試案例:
System.out.println(test()); public String test() { try { System.out.println("try block"); if (new Random().nextBoolean()) return "直接返回"; else return test2(); } finally { System.out.println("finally block"); } } public String test2() { System.out.println("return statement"); //return語句執行以後才執行finally語句 return "調用方法返回"; }
運行結果:
try block finally block 直接返回
或
try block return statement finally block 調用方法返回
說明try中的return語句先執行了,但並無當即返回,而是等到finally執行結束後再返回
。
這裏你們可能會想:若是finally裏也有return語句,那麼是否是就直接返回了,try中的return就不能返回了?看下面。
finally塊中的return語句會覆蓋try塊中的return返回
System.out.println(test()); public static String test() { try { System.out.println("try block"); return "在try中返回"; } finally { System.out.println("finally block"); return "在finally中返回"; } // return "finally外面的return就變成不可到達語句,須要註釋掉不然編譯器報錯"; }
運行結果:
try block finally block 在finally中返回
這說明finally裏的return直接返回了
,就無論try中是否還有返回語句。
這裏還有個小細節須要注意,finally里加上return事後,finally外面的return b就變成不可到達語句了
,也就是永遠不能被執行到,因此須要註釋掉不然編譯器報錯。
若是finally語句中沒有return語句覆蓋返回值,那麼原來的返回值可能由於finally裏的修改而改變,也可能不變
測試用例:
System.out.println(test()); public static int test() { int b = 20; try { System.out.println("try block"); return b += 80; } finally { b += 10; System.out.println("finally block"); } }
運行結果:
try block finally block 100 //這是關鍵
測試用例2:
System.out.println(test()); public static List<Integer> test() { List<Integer> list = new ArrayList<Integer>(); list.add(10086); try { System.out.println("try block"); return list; } finally { list.add(10088); System.out.println("finally block"); } }
運行結果:
try block finally block [10086, 10088] //這是關鍵
這其實就是Java究竟是傳值仍是傳址的問題了
,簡單來講就是:Java中只有傳值沒有傳址
。
這裏你們可能又要想:是否是每次返回的必定是try中的return語句呢?那麼finally外的return不是一點做用沒嗎?請看下面
try塊裏拋異常的return語句在異常的狀況下不會被執行,這樣具體返回哪一個看狀況
public class TestFinally { public static void main(String[] args) { System.out.println(test()); } public static int test() { int b = 0; try { System.out.println("try block"); b = b / 0; return b += 1; } catch (Exception e) { b += 10; System.out.println("catch block"); } finally { b += 100; System.out.println("finally block"); } return b; } }
運行結果是:
try block catch block finally block 110
這裏因 爲在return以前發生了異常,因此try中的return不會被執行到
,而是接着執行捕獲異常的 catch 語句和最終的 finally 語句,此時二者對b的修改都影響了最終的返回值,這時最後的 return b 就起到做用了。
這裏你們可能又有疑問:若是catch中有return語句呢?固然只有在異常的狀況下才有可能會執行,那麼是在 finally 以前就返回嗎?看下面。
當發生異常後,catch中的return執行狀況與未發生異常時try中return的執行狀況徹底同樣
public class TestFinally { public static void main(String[] args) { System.out.println(test()); } public static int test() { int b = 0; try { System.out.println("try block"); b = b / 0; return b += 1; } catch (Exception e) { b += 10; System.out.println("catch block"); return 10086; } finally { b += 100; System.out.println("finally block"); } //return b; } }
運行結果:
try block catch block finally block 10086
說明了發生異常後,catch中的return語句先執行,肯定了返回值後再去執行finally塊,執行完了catch再返回,也就是說狀況與try中的return語句執行徹底同樣。
Java包含兩種異常:checked異常和unchecked異常:
java.lang.Exception
類,Checked異常必須經過try-catch被顯式地捕獲
或者經過throws
子句進行傳遞。java.lang.RuntimeException
類,是那些可能在 Java 虛擬機正常運行期間拋出的異常
,unchecked異常能夠即沒必要捕獲也不拋出。運行時異常
運行時異常咱們通常不處理,當出現這類異常的時候程序會由虛擬機接管。好比,咱們歷來沒有去處理過NullPointerException,並且這個異常仍是最多見的異常之一。
出現運行時異常的時候,程序會將異常一直向上拋,一直拋到遇處處理代碼,若是沒有catch塊進行處理,到了最上層,若是是多線程就有Thread.run()拋出,若是不是多線程那麼就由main.run()拋出。拋出以後,若是是線程,那麼該線程也就終止了,若是是主程序,那麼該程序也就終止了。
其實運行時異常的也是繼承自Exception,也能夠用catch塊對其處理,只是咱們通常不處理罷了,也就是說,若是不對運行時異常進行catch處理,那麼結果不是線程退出就是主程序終止。若是不想終止,那麼咱們就必須捕獲全部可能出現的運行時異常。
兩種異常的使用場景
功能
的角度來說是等價的,能夠用checked異常實現的功能必然也能夠用unchecked異常實現,反之亦然。public class Test { public static void main(String[] args) { try { new Test().testException(); } catch (MyException e) { e.printStackTrace(); System.out.println("調用拋出checked異常的方法時,一樣必須經過try-catch顯式地捕獲或者經過throws子句進行傳遞"); } } void testException() throws MyException { throw new MyException("拋出checked異常時,必須經過try-catch顯式地捕獲或者經過throws子句進行傳遞"); } class MyException extends Exception { MyException(String s) { super(s); } } }
和上面相比,只需把自定義的異常由繼承自Exception改成繼承自RuntimeException便可。
因爲RuntimeException繼承自Exception,因此修改後上面其餘代碼都不須要改變就能夠正常使用。但RuntimeException能夠即沒必要捕獲也不拋出:
public class Test { public static void main(String[] args) { new Test().testException(); } void testException() { throw new MyException("unchecked異常能夠即沒必要捕獲也不拋出"); } class MyException extends RuntimeException { MyException(String s) { super(s); } } }
public class Test { public static void main(String[] args) { try { start(); } catch (IOException e) { e.printStackTrace(); } } public static void start() { System.out.println("這個方法並無聲明會拋出checked 異常,例如IOException"); } }
上面的代碼編譯是通不過的:
由於IOException是checked異常,而start方法並無拋出IOException,編譯器將在處理IOException時報錯。
可是若是你將IOException改成Exception,編譯器報錯將消失,由於Exception能夠用來捕捉全部運行時異常(包括unchecked異常),這樣就不須要聲明拋出語句。
將上例中的 IOException 改成 unchecked 異常也是能夠的,例如改成 NullPointerException
ClassNotFoundException
,CloneNotSupportedException
,DataFormatException,IllegalAccessException,InterruptedException
,IOException
,NoSuchFieldException
,NoSuchMethodException
,ParseException,TimeoutException
,XMLParseExceptionClassCastException
,IllegalArgumentException
,IllegalStateException,IndexOutOfBoundsException
,NoSuchElementException,NullPointerException
,SecurityException,SystemException,UnsupportedOperationExceptionOutOfMemoryError
、StackOverflowError
、NoClassDefFoundError、UnsatisfiedLinkError、IOError
、ThreadDeath
、ClassFormatError、InternalError、UnknownError2019-4-26