2009-09-02 136 views
38

嘿,我正在寫一個網絡應用程序,我在其中讀取一些自定義二進制格式的數據包。我開始一個後臺線程來等待傳入的數據。問題是,編譯器不讓我把任何代碼拋出(檢查)異常到run()。它說:如何從java線程拋出一個檢查異常?

run() in (...).Listener cannot implement run() in java.lang.Runnable; overridden method does not throw java.io.IOException

我想要異常殺死線程,並讓它在父線程中的某處被捕獲。這是可能實現或我必須處理線程中的每個異常

+1

看一看以下的答案: [如何捕捉從一個線程的異常] [1] [1]:http://stackoverflow.com/questions/ 6546193 /如何抓住線程中的異常 – 2013-01-01 06:14:54

回答

36

警告:如果您必須使用異常機制,則可能無法滿足您的需求。

如果我正確理解你,你實際上並不需要檢查異常(你已經接受了提示未檢查異常的答案),那麼簡單的監聽模式會更合適嗎?

監聽器可以住在父線程,而當你已經陷入子線程的檢查異常,你可以簡單地通知聽衆。

這意味着你有暴露,這將發生(通過public方法)的一種方式,並能夠傳遞更多信息不是例外允許。但它確實意味着在父線程和子線程之間會有一個耦合(雖然是一個鬆散的)。這將取決於您的具體情況,這是否會比用未選中的方式包裝檢查的異常有好處。

下面是一個簡單的例子(一些代碼從另一個答案借):

public class ThingRunnable implements Runnable { 
    private SomeListenerType listener; 
    // assign listener somewhere 

    public void run() { 
     try { 
      while(iHaveMorePackets()) { 
       doStuffWithPacket(); 
      } 
     } catch(Exception e) { 
      listener.notifyThatDarnedExceptionHappened(...); 
     } 
    } 
} 

耦合來自一個物體在具有成爲SomeListenerType類型的父線程。

+0

好主意。是的,你是對的,我不在乎是否檢查異常,我只是想要一個簡單的方法來引發異常。 – mik01aj 2009-09-02 21:18:14

+0

偵聽器代碼是否仍然實際在子線程中運行?我知道它現在在父線程中有很多範圍,但它仍然在子線程中運行。 – Jared 2009-12-18 15:50:54

+0

它會在子線程中運行,但它與要求的內容相匹配,我想。 – Grundlefleck 2009-12-18 18:51:29

3

如果在引發異常時確實無法做任何有用的事情,可以將檢查的異常包裝在RuntimeException中。

try { 
    // stuff 
} catch (CheckedException yourCheckedException) { 
    throw new RuntimeException("Something to explain what is happening", yourCheckedException); 
} 
+4

在拋出異常時總是添加說明,即使它只是要打包。 拋出新的RuntimeException(「包裝異常,讓它冒泡到foo.bar.Main()」,e)中的捕手。 當代碼中斷時,在凌晨3點調用的那些將在他們盯着堆棧跟蹤時感激它。 – 2009-09-02 19:06:22

1

如果您的線程代碼拋出RuntimeExpection,則不需要添加run()throw Exception。

但使用此解決方案只有在適當的時候,因爲這可能是一個壞的初步實踐: http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html

任何RuntimeException的或未經檢查的異常可以幫助你。也許你需要創建自己的RuntimeException

+2

請注意,此方法將隱藏父線程的異常。 – erickson 2009-09-02 18:15:07

3

線程不能將異常拋出到任何其他線程(或主線程)。你不能讓繼承的run()方法拋出任何檢查的異常,因爲你只能拋出少於繼承的代碼,而不是更多。

+0

關於不能直接將異常「拋出」到父線程的好處。 – erickson 2009-09-02 18:16:14

0

在你的代碼是在某種循環的假設,你會寫:

public class ThingRunnable implements Runnable { 
    public void run() { 
    try { 
     while(iHaveMorePackets()) { 
     doStuffWithPacket() 
     } 
    } catch(Exception e) { 
     System.out.println("Runnable terminating with exception" + e); 
    } 
    } 
} 

異常將自動中斷你出你的循環,並在運行結束()方法,線程將停止。

+0

小的觀點:你的例子中有一個實現的接口,應該是「public class ThingRunnable implements Runnable」。 – Grundlefleck 2009-09-02 20:14:00

+0

謝謝,Grundlefleck,你絕對是對的:-)看看在你有足夠的咖啡之前回答問題會發生什麼。我已將文字更新爲「班」。 – AlBlue 2009-09-03 08:29:43

7

我所做的是在線程中捕獲異常並將其存儲爲Runnable的成員變量。這個異常然後通過Runnable上的getter公開。然後,我掃描父級的所有線程,看看是否有異常,並採取適當的措施。

+1

我可以問(如果我的回答錯誤),爲什麼要將變量存儲爲getter並掃描它而不是使用偵聽器機制? – Grundlefleck 2009-09-02 20:15:22

+0

公平的問題。這兩種方法都有效。我想我會在下次嘗試你的建議,看看我是否喜歡這樣。 – 2009-09-02 20:20:10

+0

看到我對Grundlefleck的答案的評論 - 我相信這個解決方案確實將異常處理恢復回到父線程,而Grundlefleck的解決方案不會。 (Grundlefleck修復了範圍問題 - 但是這個修復了與線程上下文真正相關的問題。) – Jared 2009-12-18 15:51:52

45

爲了能夠將異常發送到父線程,可以將您的後臺線程放在Callable(它允許拋出異常),然後將其傳遞給some Executorsubmit方法。提交方法將返回一個Future,然後您可以使用該方法來獲取該異常(其get方法將拋出包含原始異常的ExecutionException)。

+3

這對我來說看起來太複雜了。但是,無論如何,謝謝:) – mik01aj 2009-09-02 21:21:16

31

此答案基於Esko Luontola,但它提供了一個工作示例。

與Runnable接口的run()方法不同,Callable的call()方法允許拋出一些異常。下面是一個實現的例子:

public class MyTask implements Callable<Integer> { 

    private int numerator; 
    private int denominator; 

    public MyTask(int n, int d) { 
     this.numerator = n; 
     this.denominator = d; 
    } 

    @Override 
    // The call method may throw an exception 
    public Integer call() throws Exception { 
     Thread.sleep(1000); 
     if (denominator == 0) { 
      throw new Exception("cannot devide by zero"); 
     } else { 
      return numerator/denominator; 
     } 
    } 

} 

執行人提供了一種機制,以一個線程中運行一個可調用和處理任何類型的異常:

public class Main { 

    public static void main(String[] args) { 

     // Build a task and an executor 
     MyTask task = new MyTask(2, 0); 
     ExecutorService threadExecutor = Executors.newSingleThreadExecutor(); 

     try { 
      // Start task on another thread 
      Future<Integer> futureResult = threadExecutor.submit(task); 

      // While task is running you can do asynchronous operations 
      System.out.println("Something that doesn't need the tasks result"); 

      // Now wait until the result is available 
      int result = futureResult.get(); 
      System.out.println("The result is " + result); 
     } catch (ExecutionException e) { 
      // Handle the exception thrown by the child thread 
      if (e.getMessage().contains("cannot devide by zero")) 
       System.out.println("error in child thread caused by zero division"); 
     } catch (InterruptedException e) { 
      // This exception is thrown if the child thread is interrupted. 
      e.printStackTrace(); 
     } 
    } 
} 
+6

這解決了未捕獲異常的問題,但可能會引入它自己的問題。調用get()會阻塞,直到可調用任務返回。這意味着你不再從後臺線程中獲得任何並行性。更糟糕的是,如果任務是連續/長時間運行的(例如在循環中等待網絡數據包),那麼'Callable'將不會返回,並且您的主線程將永久阻塞。 我並不是說暗示這種模式沒有意義(我確定有很多),只是需要小心。 – theisenp 2013-03-28 20:28:20

+0

好點。在一個真實的例子中,你不會在'submit'之後調用'get'。 – 2013-03-30 21:23:39

+0

在實際的例子中,你可以並行地啓動N個後臺操作,並等待M <= N的M個後臺操作的結果做出決定。在這種情況下,您會在提交後立即致電。 – Ameliorator 2015-04-06 13:03:19

-1

包裝一下你的例外內RuntimeException似乎做的伎倆。

someMethod() throws IOException 
{ 
    try 
    { 
     new Thread(() -> 
     { 
      try 
      { 
       throw new IOException("a checked exception thrown from within a running thread"); 
      } 
      catch(IOException ex) 
      { 
       throw new RuntimeException("a wrapper exception", ex); // wrap the checked exception inside an unchecked exception and throw it 
      } 
     }).start(); 
    } 
    catch(RuntimeException ex) // catch the wrapped exception sent from within the thread 
    { 
     if(ex.getCause() instanceof IOException) 
      throw ex.getCause; // unwrap the checked exception using getCause method and use it however you need 
     else 
      throw ex; 
    } 
} 
+0

它不起作用 – Richard 2017-03-25 13:24:53