多執行緒中如何進行異常處理?
本文為joshua317原創文章,轉載請註明:轉載自joshua317部落格https://www.joshua317.com/article/240
一、Thread的預設異常處理
執行緒不允許丟擲未捕獲的checked exception(比如sleep時的InterruptedException),也就是說各個執行緒需要自己把自己的checked exception處理掉。我們可以檢視一下Thread類的run()方法宣告,方法宣告上沒有對丟擲異常進行任何約束。
//Thread類中 @Override public void run() { if (target != null) { target.run(); } } //Runnable介面中 public abstract void run();
JVM的這種設計源自於這樣一種理念:“執行緒是獨立執行的程式碼片斷,執行緒的問題應該由執行緒自己來解決,而不要委託到外部。”基於這樣的設計理念,在Java中,執行緒方法的異常(無論是checked exception還是unchecked exception),都應該線上程程式碼邊界之內(run方法內)進行try catch並處理掉。換句話說,我們不能捕獲從執行緒中逃逸的異常。
二、未捕獲的異常如何處理的
一個異常被丟擲後,如果沒有被捕獲處理,則會一直向上拋。異常一旦被Thread.run() 丟擲後,就不能在程式中對異常進行捕獲,最終只能由JVM捕獲。
package com.joshua317; public class ThreadException { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } } class MyThread extends Thread { @Override public void run() { System.out.println("執行緒名:" + Thread.currentThread().getName()); //發生異常 int i = 1 / 0; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
接下來我們嘗試對異常進行捕獲,看看是否能捕獲到
package com.joshua317; public class ThreadException { public static void main(String[] args) { try { MyThread myThread = new MyThread(); myThread.start(); }catch (Exception e) { System.out.println("捕獲執行緒丟擲的異常!" + e.getMessage()); } } } class MyThread extends Thread { @Override public void run() { System.out.println("執行緒名:" + Thread.currentThread().getName()); //發生異常 int i = 1 / 0; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
結果我們發現,我們嘗試在main方法中對執行緒中丟擲的異常進行捕獲,但是毫無作用。
三、那麼,JVM如何處理執行緒中丟擲的異常的呢
檢視Thread類的原始碼,我們可以看到有個dispatchUncaughtException方法,此方法就是用來處理執行緒中丟擲的異常的。JVM會呼叫dispatchUncaughtException方法來尋找異常處理器(UncaughtExceptionHandler),處理異常。
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
//向handler分派未捕獲的異常。該方法僅由JVM呼叫。
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
/**
* Returns the handler invoked when this thread abruptly terminates
* due to an uncaught exception. If this thread has not had an
* uncaught exception handler explicitly set then this thread's
* <tt>ThreadGroup</tt> object is returned, unless this thread
* has terminated, in which case <tt>null</tt> is returned.
* @since 1.5
* @return the uncaught exception handler for this thread
*/
// 獲取用來處理未捕獲異常的handler,如果沒有設定則返回當前執行緒所屬的ThreadGroup
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
UncaughtExceptionHandler必須顯示的設定,否則預設為null。若為null,則使用執行緒預設的handler,即該執行緒所屬的ThreadGroup。ThreadGroup自身就是一個handler,檢視ThreadGroup的原始碼就可以發現,ThreadGroup實現了Thread.UncaughtExceptionHandler介面,並實現了預設的處理方法。預設的未捕獲異常處理器處理時,會呼叫 System.err 進行輸出,也就是直接列印到控制檯了。
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)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
由此可知,最終JVM是呼叫未捕獲的異常處理器的uncaughtException()方法來處理異常的,並且是直接列印到控制檯。
四、如何自定義處理執行緒異常
如果我們要自己處理異常,該怎麼辦呢?通過前面的分析,我們已經知道了執行緒會使用預設的未捕獲異常處理器來處理異常。自然我們可以想到,是否可以自定義未捕獲異常處理器,覆蓋掉預設的捕獲異常處理器。實際上,Thead確實已經提供了一個setUncaughtExceptionHandler方法,我們只需要將自定義未捕獲異常處理器作為引數傳入進入就可以了。
接下來我們自定義處理執行緒異常
package com.joshua317;
public class ThreadException {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setUncaughtExceptionHandler();
myThread.start();
}
}
class MyThread extends Thread {
@Override
public void run()
{
System.out.println("執行緒名:" + Thread.currentThread().getName());
//發生異常
int i = 1 / 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void setUncaughtExceptionHandler()
{
this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕獲執行緒丟擲的異常:" + e.getMessage());
}
});
}
}
五、執行緒池中自定義處理異常
要自定義處理異常,只需要為執行緒提供一個自定義的UncaughtExceptionHandler。而線上程池中,該如何批量的為所有執行緒設定UncaughtExceptionHandler呢?我們知道,執行緒池中的執行緒是由執行緒工廠建立的。我們可以跟蹤ThreadPoolExecutor構造方法的原始碼,最終定位到DefaultThreadFactory類,該類中有個newThread()方法,這就是執行緒產生的源頭了。
//ThreadPoolExecutor類中
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//Executors類中
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
//DefaultThreadFactory類中
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
找到了執行緒建立的源頭,我們就可以實現一個自己的執行緒工廠,並覆蓋預設的newThread方法,為新建立的執行緒提供一個UncaughtExceptionHandler,即可達到我們的目的。由於DefaultThreadFactory是Executors類的內部類,我們不能直接繼承該類,只能實現該工廠類的介面——ThreadFactory介面,來實現我們自己的執行緒工廠。
package com.joshua317;
import java.util.concurrent.ThreadFactory;
public class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread();
//自定義UncaughtExceptionHandler
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
return t;
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕獲執行緒丟擲的異常:" + e.getMessage());
}
}
本文為joshua317原創文章,轉載請註明:轉載自joshua317部落格https://www.joshua317.com/article/240