線程泄漏
當單線程的控制檯程序由於發生了一個未捕獲的異常而終止時,程序將停止運行,併產生與程序正常輸出非常不同的棧追蹤信息,這與典型的程序輸出不同,當一個程序發生了異常說明有不穩定的因素存在。如果在併發程序中線程失敗就沒那麼容易發現了。棧追蹤可能會從控制檯輸出,但是沒有人會一直在看控制檯,並且,當線程失敗的時候,應用程序可能看起來仍在工作。就象程序能跑在50個線程的線程池上,也能夠跑在49個線程的線程池上,區別在於50個人乾的活要比49個人乾的活多得多。
導致線程提前死亡的最主要原因是RuntimeException
。由於這些異常表示出現了某種編程錯誤或者其它不可修復的錯誤,因此它們通常不會被捕獲。它們不會在調用棧中逐層傳遞,而是默認地在控制檯中輸出棧追蹤信息,並終止線程。
處理未捕獲的異常
在Thread API
中,同樣提供了UncaughtExceptionHandler
,它能檢測出某個線程由於未捕獲的異常而終結的情況。通過使用UncaughtExceptionHandler
異常處理器就能有效地防止線程泄漏問題。
當一個線程由於未捕獲異常而退出時,JVM會把這個事件報告給應用程序提供的UncaughtExceptionHandler
異常處理器(如以下代碼所示)。如果沒有提供任何異常處理器,那麼默認的行爲是將棧追蹤信息輸出到System.err
。
// UncaughtExceptionHandler 接口
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
異常處理器如何處理未捕獲異常,取決於對服務質量的需求。最常見的響應方式是將一個錯誤信息以及相應的棧追蹤信息寫入應用程序日誌中,如以下代碼所示。異常處理器還可以採取更直接的響應,例如嘗試重新啓動線程,關閉應用程序,或者執行其他修復或診斷等操作。
// 將異常寫入日誌的 UncaughtExceptionHandler
public class UEHLogger implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.SEVERE, "Thread terminated with exception:" + t.getName());
}
}
在運行時間較長的應用程序中,通常會爲所有線程的未捕獲異常指定同一個異常處理器,並且該處理器至少會將異常信息記錄到日誌中。
單線程設置異常處理器
下面通過一個例子演示異常處理器的使用方式:
public class UEHTest {
private static final Logger logger = Logger.getLogger("UEHTest");
public static void main(String[] args) throws IOException {
MyThreadFactory mtf = new MyThreadFactory();
Thread t = mtf.newThread(new Runnable() {
@Override
public void run() {
for (int i=0; i<50 ; i++) {
if (i == 40)
throw new RuntimeException("發生了未捕獲異常");
}
}
});
t.start();
}
// 定義線程工廠,以產生設置異常處理器和編號的線程
static class MyThreadFactory implements ThreadFactory {
private final AtomicInteger threadCount = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new UEHLogger());
t.setName("my-thread-" + threadCount.incrementAndGet());
return t;
}
}
// 定義一個異常處理器,當線程發生未捕獲異常時記錄日誌
static class UEHLogger implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.log(Level.SEVERE, t.getName() + " throwed exception...", e);
}
}
}
運行結果:
線程池設置異常處理器
要爲線程池中的所有線程設置一個UncaughtExceptionHandler
,需要爲ThreadPoolExecutor
的構造函數提供一個ThreadFactory
。標準線程池允許當發生未捕獲異常時結束線程,但由於使用了一個try-finally
代碼塊來接收通知,因此當線程結束時,將有新的線程來代替它,如果沒有提供未捕獲異常處理器或者其它的故障通知機制,那麼任務會悄悄失敗,從而導致極大的混亂。
異常處理器結合線程池:
public class UEHThreadPoolTest {
private static final Logger logger = Logger.getLogger("UEHThreadPoolTest");
public static void main(String[] args) throws IOException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 5, 0L,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
new MyThreadFactory());
tpe.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i == 40)
throw new RuntimeException("發生了未捕獲異常");
}
}
});
}
// 定義線程工廠,以產生設置異常處理器和編號的線程
static class MyThreadFactory implements ThreadFactory {
private final AtomicInteger threadCount = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new UEHLogger());
t.setName("my-thread-" + threadCount.incrementAndGet());
return t;
}
}
// 定義一個異常處理器,當線程發生未捕獲異常時記錄日誌
static class UEHLogger implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.log(Level.SEVERE, t.getName() + " throwed exception...", e);
}
}
}
運行結果:
只有通過execute
提交的任務,才能將它拋出的異常交給未捕獲異常處理器。而通過submit
提交的任務,無論是拋出的未檢查異常還是已檢查異常,都將被認爲是任務返回狀態的一部分。如果一個由submit
提交的任務由於拋出了異常而結束,那麼這個異常將被Future.get
封裝在ExecutionException
中重新拋出。