讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議122:使用線程異常處理器提升系統可靠性 >

建議122:使用線程異常處理器提升系統可靠性

我們要編寫一個Socket應用,監聽指定端口,實現數據包的接收和發送邏輯,這在早期系統間進行數據交互是經常使用的,這類接口通常需要考慮兩個問題:一是避免線程阻塞,保證接收的數據盡快處理;二是接口的穩定性和可靠性問題,數據包很複雜,接口服務的系統也很多,一旦守候線程出現異常就會導致Socket停止響應,這是非常危險的,那我們有什麼辦法來避免嗎?

Java 1. 5版本以後在Thread類中增加了setUncaughtExceptionHandler方法,實現了線程異常的捕捉和處理。可能大家會有一個疑問:如果Socket應用出現了不可預測的異常是否可以自動重啟呢?其實使用線程異常處理器很容易解決,我們來看一個異常處理器應用的例子,代碼如下:


class TcpServer implements Runnable{

//創建後即運行

public TcpServer(){

Thread t=new Thread(this);

t.setUncaughtExceptionHandler(new TcpServerExceptionHandler());

t.start();

}

@Override

public void run(){

//正常業務運行,運行3秒

for(int i=0;i<3;i++){

try{

Thread.sleep(1000);

System.out.println(\"系統正常運行:\"+i);

}catch(InterruptedException e){

e.printStackTrace();

}

}

//拋出異常

throw new RuntimeException();

}

//異常處理器

private static class TcpServerExceptionHandler implements

Thread.UncaughtExceptionHandler{

@Override

public void uncaughtException(Thread t, Throwable e){

//記錄線程異常信息

System.out.println(\"線程\"+t.getName()+\"出現異常,自行重啟,請分析原因。\");

e.printStackTrace();

//重啟線程,保證業務不中斷

new TcpServer();

}

}

}


這段代碼的邏輯比較簡單,在TcpServer類創建時即啟動一個線程,提供TCP服務,例如接收和發送文件,具體邏輯在run方法中實現。同時,設置了該線程出現運行期異常(也就是Uncaught Exception)時,由TcpServerExceptionHandler異常處理器來處理。那TcpServerExceptionHandler異常處理器做什麼事呢?兩件事:

記錄異常信息,以便查找問題。

重新啟動一個新線程,提供不間斷的服務。

有了這兩點,TcpServer就可以穩定地運行了,即使出現異常也能自動重啟。客戶端代碼比較簡單,只需要new TcpServer()即可,運行結果如下:


系統正常運行:0

系統正常運行:1

系統正常運行:2

線程Thread-0出現異常,自行重啟,請分析原因。

java.lang.RuntimeException

at TcpServer.run(Client.java:30)

at java.lang.Thread.run(Thread.java:619)

系統正常運行:0

系統正常運行:1

系統正常運行:2

線程Thread-1出現異常,自行重啟,請分析原因。

java.lang.RuntimeException

at TcpServer.run(Client.java:30)

at java.lang.Thread.run(Thread.java:619)


從運行結果上也可以看出,當Thread-0出現異常時,系統自動啟動了Thread-1線程,繼續提供服務,大大提高了系統的可靠性。

這段程序只是一個示例程序,若要在實際環境中應用,則需要注意以下三個方面:

(1)共享資源鎖定

如果線程異常產生的原因是資源被鎖定,自動重啟應用只會增加系統的負擔,無法提供不間斷服務。例如一個即時通信服務器(XMPP Server)出現信息不能寫入的情況時,即使再怎麼重啟服務,也是無法解決問題的。在此情況下最好的辦法是停止所有的線程,釋放資源。

(2)髒數據引起系統邏輯混亂

異常的產生中斷了正在執行的業務邏輯,特別是如果正在執行一個原子操作(像即時通信服務器的用戶驗證和簽到這兩個事件應該在一個操作中處理,不允許出現驗證成功但簽到不成功的情況),但如果此時拋出了運行期異常就有可能會破壞正常的業務邏輯,例如出現用戶認證通過了,但簽到不成功的情況,在這種情景下重啟應用服務器,雖然可以提供服務,但對部分用戶則產生了邏輯異常。

(3)內存溢出

線程異常了,但由該線程創建的對象並不會馬上回收,如果再重新啟動新線程,再創建一批新對象,特別是加入了場景接管,就非常危險了,例如即時通信服務,重新啟動一個新線程必須保證原在線用戶的透明性,即用戶不會察覺服務重啟,在此種情況下,就需要在線程初始化時加載大量對像以保證用戶的狀態信息,但是如果線程反覆重啟,很可能會引起OutOfMemory內存洩露問題。