【问题标题】:How to throw a checked exception from a java thread?java - 如何从java线程抛出检查异常?
【发布时间】:2009-09-02 17:54:50
【问题描述】:

嘿,我正在编写一个网络应用程序,我在其中读取一些自定义二进制格式的数据包。我正在启动一个后台线程来等待传入的数据。问题是,编译器不允许我将任何抛出(检查)异常的代码放入run()。它说:

run() in (...).Listener 无法在 java.lang.Runnable 中实现 run();被覆盖的方法不会抛出 java.io.IOException

我希望异常杀死线程,并让它在父线程的某个地方被捕获。这可能实现还是我必须处理线程内部的每个异常?

【问题讨论】:

标签: java exception thread-exceptions


【解决方案1】:

为了能够将异常发送到父线程,您可以将后台线程放在Callable 中(它也允许抛出检查异常),然后将其传递给some Executorsubmit 方法。 submit 方法将返回一个Future,然后您可以使用它来获取异常(它的get 方法将抛出一个包含原始异常的ExecutionException

【讨论】:

    【解决方案2】:

    警告:如果您必须使用异常机制,这可能无法满足您的需求。

    如果我对您的理解正确,您实际上不需要检查异常(您已接受建议未检查异常的答案),那么简单的侦听器模式是否更合适?

    侦听器可以存在于父线程中,当您在子线程中捕获到已检查的异常时,您可以简单地通知侦听器。

    这意味着您有一种方法可以揭示这将发生(通过公共方法),并且能够传递比异常所允许的更多的信息。但这确实意味着父线程和子线程之间会有一个耦合(尽管是松散的)。这取决于您的具体情况,这是否比用未经检查的异常包装检查的异常更有好处。

    这是一个简单的例子(一些代码是从另一个答案中借来的):

    public class ThingRunnable implements Runnable {
        private SomeListenerType listener;
        // assign listener somewhere
    
        public void run() {
            try {
                while(iHaveMorePackets()) { 
                    doStuffWithPacket();
                }
            } catch(Exception e) {
                listener.notifyThatDarnedExceptionHappened(...);
            }
        }
     }
    

    耦合来自父线程中必须是SomeListenerType类型的对象。

    【讨论】:

    • 好主意。是的,你是对的,我不在乎是否检查了异常,我只是想要一种简单的方法来引发异常。
    • 监听代码是否还没有真正在子线程中运行?我知道它现在在父线程中的大部分范围......但它仍然在子线程中运行。
    • 它会在子线程中运行,但我认为它符合要求的内容。
    【解决方案3】:

    此答案基于 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;
            }
        }
    
    }
    

    Executor 提供了一种在线程内运行 Callable 并处理任何类型异常的机制:

    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();
            }
        }
    }
    

    【讨论】:

    • 这解决了未捕获异常的问题,但可能会引入它自己的问题。对get() 的调用将阻塞,直到可调用任务返回。这意味着您不再从后台线程中获得任何并行性。更糟糕的是,如果任务是连续/长时间运行的(例如在循环中等待网络数据包),Callable永远返回并且您的主线程被永久阻塞。我并不是说没有任何应用程序对这种模式有意义(我相信有很多),只是你需要小心。
    • 好点。在实际示例中,您不会在 submit 之后立即调用 get
    • 在一个真实的例子中,您可能会并行启动 N 个后台操作,并等待 M
    【解决方案4】:

    我所做的是在线程中捕获异常并将其存储为 Runnable 的成员变量。然后通过 Runnable 上的 getter 公开此异常。然后,我扫描来自父级的所有线程以查看是否有异常,并采取适当的措施。

    【讨论】:

    • 请问(以防我的回答错了),为什么将变量存储为 getter 并扫描它而不是使用侦听器机制?
    • 公平的问题。任何一种方法都有效。我想下次我会试试你的建议,看看我是否愿意。
    • 查看我对 Grundlefleck 答案的评论 - 我相信这个解决方案确实将异常处理返回到父线程,而 Grundlefleck 的解决方案没有。 (Grundlefleck 修复了范围问题 - 但这个修复了与线程上下文真正相关的问题。)
    • 这种方法的优点是没有耦合。 Grundlefleck 的解决方案是对 Listener 类的引用,而 Don Branson 的解决方案使“Runnable”完全独立于其他代码。
    【解决方案5】:

    如果在引发异常时您确实无法做任何有用的事情,您可以将检查的异常包装在 RuntimeException 中。

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

    【讨论】:

    • 总是在抛出异常时添加描述,即使它只是为了包装。 throw new RuntimeException("包装异常以允许它冒泡到 foo.bar.Main() 中的捕手", e);那些在代码中断时在凌晨 3 点调用的人会在他们盯着堆栈跟踪时欣赏它。
    【解决方案6】:

    线程不能将异常抛给任何其他线程(也不能抛给主线程)。而且你不能让继承的 run() 方法抛出任何已检查的异常,因为你只能抛出比继承的代码少的东西,而不是更多。

    【讨论】:

    • 关于不能直接将异常“抛出”到父线程的好处。
    【解决方案7】:

    如果你的线程代码抛出了 RuntimeExpection,你不需要添加 run() throw Exception。

    但请仅在适当的时候使用此解决方案,因为这可能是一种不好的做法: http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html

    任何 RuntimeException 或未经检查的异常都可以帮助您。也许您需要创建自己的 RuntimeException

    【讨论】:

    • 请注意,这种方法会对父线程隐藏异常。
    【解决方案8】:

    假设您的代码处于某种循环中,您会这样写:

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

    异常会自动让你跳出循环,并且在 run() 方法结束时,线程将停止。

    【讨论】:

    • 小点:你的例子有一个接口,里面有实现,应该是“public class ThingRunnable implements Runnable”。
    • 谢谢,Grundlefleck,你是绝对正确的 :-) 当你在一天喝够咖啡之前回答问题时会发生什么。我已将文本更新为“类”。
    【解决方案9】:

    使用这个 Runnable 创建你的线程:

    public abstract class TryRunner implements Runnable{
        protected abstract void tryToRun();
        protected void onException(Exception e){}
    
        @Override 
        final public void run() { 
            try{ tryToRun(); }catch(Exception e){ e.printStackTrace(); onException(e); } 
        }
    }
    

    【讨论】:

      【解决方案10】:

      将异常包装在 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;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-18
        • 2011-09-21
        • 1970-01-01
        • 2012-01-11
        • 1970-01-01
        • 1970-01-01
        • 2013-08-23
        • 1970-01-01
        相关资源
        最近更新 更多