java線程異常處理

2004 年 8 月 10 日

跟蹤無法預期的運行時異常可能是一件又慢又費力的事情,只獲得默認線程名稱和堆棧跟蹤通常是不夠的。在 馴服 Tiger 這一期專欄中,Java 開發人員 John Zukowski 向您展示瞭如何通過替代默認行爲來定製輸出。他還對比了通過細分 ThreadGroup 定製輸出的老方法與通過提供自己的 UncaughtExceptionHandler 定製輸出的新方法。

 

雖然我們不想創建在無法預期時拋出運行時異常的程序,但這種情況還是會發生——尤其是第一次運行復雜程序時。通常是使用默認行爲、打印堆棧溢出和結束線程的生命來處理這些異常。

從哪裏發現默認行爲?每個線程都屬於一個由 java.lang.ThreadGroup 類表示的線程組。顧名思義,線程組允許您將線程組合在一起。您可能是爲了方便而將線程組合,例如,一個線程池中的所有線程都屬於組 X,而另一個池的所有線程則屬於組 Y,或者是爲了訪問控制而將線程進行組合。組 X 中的線程無權訪問或改變組 Y 中的線程,除非它們都在同一線程組內(或在一個子組內)。

在 Tiger 之前, ThreadGroup 類提供了一種處理未捕獲異常的方法: ThreadGroupuncaughtException() 方法。如果異常不是 ThreadDeath ,則將線程的名稱和堆棧回溯(stack backtrace)發送到 System.err 。但是 Tiger 添加了另一種方法: Thread.UncaughtExceptionHandler 接口。細分 ThreadGroup 或安裝該新接口的實現都允許您更改默認行爲。我們將對 Tiger 之前和之後提供的方法都進行研究。

使用 ThreadGroup 的定製行爲

發生未捕獲的異常時,默認行爲是將堆棧溢出打印輸出到系統錯誤( System.err )中,如清單 1 中所示。不需要使用任何命令參數來啓動程序。


清單 1. 線程溢出示例

public class SimpleDump {
  public static void main(String args[]) {
    System.out.println(args[0]);
  }
}

 

不使用任何參數運行該程序將生成清單 2 中的輸出。儘管它不是一個很長的堆棧跟蹤,但它是一個完整的堆棧跟蹤。


清單 2. 默認線程溢出輸出

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at SimpleDump.main(SimpleDump.java:3)

 

正如 Java 平臺的許多東西一樣,如果不喜歡默認行爲,您可以對其進行更改。在 Java 平臺的 Tiger 版以前的版本中,不能替代所有線程的默認行爲,但是可以創建一個新的 ThreadGroup ,並更改在該組內創建的任何線程的默認行爲。您可以重寫 uncaughtException(Thread t, Throwable e) 方法來定製該行爲。然後,當發生未預料的運行時異常時,該線程組內創建的任何線程都將獲得新的行爲。不過,最好是修復基礎問題,我將提供一個簡單的示例,說明更改默認行爲所必需的步驟。清單 3 展示了將執行代碼放入新線程的調整過的測試程序:


清單 3. 調整過的線程溢出示例

public class WindowDump {
  public static void main(String args[]) throws Exception {
    ThreadGroup group = new LoggingThreadGroup("Logger");
    new Thread(group, "myThread") {
      public void run() {
        System.out.println(1 / 0);
      }
    }.start();
  }
}

 

LoggingThreadGroup 類是一個新的內容,清單 4 中顯示了它的定義。爲了進行說明,通過重寫 uncaughtException() 方法實現的特殊行爲將在一個彈出窗口中顯示該異常,這項操作是在特殊 Handler 的幫助下使用 Java Logging API 來完成的。


清單 4. LoggingThreadGroup 的定義

import java.util.logging.*;
public class LoggingThreadGroup extends ThreadGroup {
  private static Logger logger;
  public LoggingThreadGroup(String name) {
    super(name);
  }
  public void uncaughtException(Thread t, Throwable e) {
    // Initialize logger once
    if (logger == null) {
      logger = Logger.getLogger("example");
      Handler handler = LoggingWindowHandler.getInstance();
      logger.addHandler(handler);
    }
    logger.log(Level.WARNING, t.getName(), e);
  }
}

 

這裏創建的定製 Handler 的類型爲 LoggingWindowHandler ,該類型的定義在清單 5 中。處理程序使用了一個支持類 LoggingWindow ,該類將異常顯示在屏幕上。 清單 6 中顯示了該類的定義。 Handlerpublic void publish(LogRecord record) 方法實現了一些重要操作。其餘操作大部分只與配置有關。


清單 5. LoggingWindowHandler 的定義

import java.util.logging.*;
public class LoggingWindowHandler extends Handler {
  private static LoggingWindow window;
  private static LoggingWindowHandler handler;
  private LoggingWindowHandler() {
    configure();
    window = new LoggingWindow("Logging window...", 400, 200);
  }
  public static synchronized LoggingWindowHandler getInstance() {
    if (handler == null) {
      handler = new LoggingWindowHandler();
    }
    return handler;
  }
  /**
   * Get any configuration properties set
   */
  private void configure() {
    LogManager manager = LogManager.getLogManager();
    String className = getClass().getName();
    String level = manager.getProperty(className + ".level");
    setLevel((level == null) ? Level.INFO : Level.parse(level));
    String filter = manager.getProperty(className + ".filter");
    setFilter(makeFilter(filter));
    String formatter =
      manager.getProperty(className + ".formatter");
    setFormatter(makeFormatter(formatter));
  }
  private Filter makeFilter(String name) {
    Filter f = null;
    try {
      Class c = Class.forName(name);
      f = (Filter)c.newInstance();
    } catch (Exception e) {
      if (name != null) {
        System.err.println("Unable to load filter: " + name);
      }
    }
    return f;
  }
  private Formatter makeFormatter(String name) {
    Formatter f = null;
    try {
      Class c = Class.forName(name);
      f = (Formatter)c.newInstance();
    } catch (Exception e) {
      f = new SimpleFormatter();
    }
    return f;
  }
  // Overridden abstract Handler methods
  
  public void close() {
  }
  public void flush() {
  }
  /**
   * If record is loggable, format it and add it to window
   */
  public void publish(LogRecord record) {
    String message = null;
    if (isLoggable(record)) {
      try {
        message = getFormatter().format(record);
      } catch (Exception e) {
        reportError(null, e, ErrorManager.FORMAT_FAILURE);
        return;
      }
      try {
        window.addLogInfo(message);
      } catch (Exception e) {
        reportError(null, e, ErrorManager.WRITE_FAILURE);
      }
    }
  }
}





清單 6. LoggingWindow 的定義

import java.awt.*;
import javax.swing.*;
public class LoggingWindow extends JFrame {
  private JTextArea textArea;
  public LoggingWindow(String title, final int width,
                                           final int height) {
  super(title);
  EventQueue.invokeLater(new Runnable() {
    public void run() {
      setSize(width, height);
      textArea = new JTextArea();
      JScrollPane pane = new JScrollPane(textArea);
        textArea.setEditable(false);
        getContentPane().add(pane);
        setVisible(true);
      }
    });
  }
  public void addLogInfo(final String data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        textArea.append(data);
      }
    });
  }
}

 

執行 清單 3 中的 WindowDump 程序將出現圖 1 中的屏幕。因爲沒有從 Logger 中刪除控制檯處理程序,所以堆棧溢出仍將出現在控制檯上。


圖 1. 記錄的堆棧跟蹤
記錄的堆棧跟蹤

發生運行時異常時,可能要做許多工作來更改發生的問題。該代碼的大部分都是 Logging Handler,但是,要執行更改,就必須細分 ThreadGroup ,重寫 uncaughtException() ,然後在該線程組中執行您的線程。不過,讓我們通過只安裝 Thread.UncaughtExceptionHandler ,來看一看 Tiger 的處理方式。

 




回頁首

 

使用 UncaughtExceptionHandler 的定製行爲

對於 Tiger, Thread 類定義中添加了一個新的公共內部類 UncaughtExceptionHandler ,更完整的名稱爲 Thread.UncaughtExceptionHandler (其他類訪問內部類時需要使用完整名稱)。接口的定義是一個方法,如圖 7 中所示:


清單 7. UncaughtExceptionHandler 的定義

public interface Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread, Throwable);
}

 

您可能沒有注意到,清單 7 中的方法與我們前面重寫的 ThreadGroup 的方法相同。實際上,現在由 ThreadGroup 類實現該接口。

新的內部類可以幫助我們瞭解下列兩對新方法,並有助於我們在 Thread 中使用它們:

  • getUncaughtExceptionHandler()setUncaughtExceptionHandler()
  • getDefaultUncaughtExceptionHandler()setDefaultUncaughtExceptionHandler()

第一對方法是 getUncaughtExceptionHandler()setUncaughtExceptionHandler() ,它們允許您爲當前線程及其後代定製行爲,從而允許二十或更多的線程擁有自己的定製行爲。不過,您更可能使用第二對方法 getDefaultUncaughtExceptionHandler()setDefaultUncaughtExceptionHandler() 。如果使用第二對方法設置默認處理程序,那麼沒有自己的異常處理程序的所有線程都將使用默認處理程序。

聽起來好像很簡單。爲了進行說明,清單 8 轉換了 清單 3 中的 ThreadGroup 友好的程序,使用新的 UncaughtExceptionHandler 接口:


清單 8. UncaughtExceptionHandler 示例

public class HandlerDump {
  public static void main(String args[]) throws Exception {
    Thread.UncaughtExceptionHandler handler = new LoggingThreadGroup("Logger");
    Thread.currentThread().setUncaughtExceptionHandler(handler);
    System.out.println(1 / 0);
  }
}

 

該程序只是將 LoggingThreadGroup 重用爲 UncaughtExceptionHandler ,並沒有創建新的處理程序實現。請注意,與原來的代碼相比,新代碼要簡潔得多。

 




回頁首

 

其他線程更改

Thread 類不僅支持使用 Tiger 添加的未捕獲異常處理程序,它還支持使用 getAllStackTraces() 獲得所有有效線程的堆棧跟蹤,或者支持使用 getStackTrace() 來只獲得當前線程的堆棧跟蹤。這兩種堆棧跟蹤都返回類型爲 java.lang.StackTraceElement 的對象, java.lang.StackTraceElement 是 Java 1.4 平臺中添加的一個類,它可以讓您生成自己的堆棧跟蹤。同時,Java 5 平臺新添加的功能是一個惟一線程標識符(可以使用 getId() 獲得該標識符)和一個新的 Thread.State 類,以及與該類相關的 getThreadState() 方法。最後一個線程更改是一個狀態枚舉表,該表是用來監視系統狀態,而不是用來同步狀態的。

 




回頁首

 

結束語

像添加未捕獲的異常處理程序這樣的簡單庫更改,可以極大地增加原代碼的可理解性。雖然在線程組級別上,新的庫代碼的功能與原來庫代碼的 相同,但新模型中的易用性和靈活性遠遠超出了將代碼調整爲更新的方式所需的時間。當然,老方法仍然可以使用,但最好將代碼更新爲最新的庫功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章