如何从java线程中抛出一个检查过的异常?

mik*_*1aj 40 java exception thread-exceptions

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

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

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

Esk*_*ola 48

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


Gru*_*eck 38

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

如果我理解正确,你实际上并不需要检查异常(你接受了建议未经检查的异常的答案)那么一个简单的监听器模式会更合适吗?

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

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

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

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

    public void run() {
        try {
            while(iHaveMorePackets()) { 
                doStuffWithPacket();
            }
        } catch(Exception e) {
            listener.notifyThatDarnedExceptionHappened(...);
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

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


a.b*_*b.d 33

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

}
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这解决了未捕获异常的问题,但可能会引入自身的问题.对`get()`的调用将阻塞,直到可调用任务返回.这意味着您不再从后台线程中获得任何并行性.更糟糕的是,如果任务是连续/长时间运行(比如在循环中等待网络数据包),`Callable`将_never_返回并且您的主线程被永久阻止.我并不是说暗示没有这种模式有意义的应用程序(我确定有很多),只是你需要小心. (7认同)

Don*_*son 7

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

  • 请问(如果我的答案是错误的),为什么将变量存储为 getter 并扫描它而不是使用侦听器机制? (2认同)

Sim*_*olt 5

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

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

  • 抛出异常时总是添加描述,即使它只是为了换行。throw new RuntimeException("包装异常以允许它冒泡到 foo.bar.Main() 中的捕获器", e); 当代码中断时在凌晨 3 点调用的那些人在盯着堆栈跟踪时会很感激。 (5认同)