在什么情况下,Future.get()会抛出ExecutionException或InterruptedException?

42

我的代码片段:

ExecutorService executor = Executors.newSingleThreadExecutor();
try {
    Task t = new Task(response,inputToPass,pTypes,unit.getInstance(),methodName,unit.getUnitKey());
    Future<SCCallOutResponse> fut = executor.submit(t);
    response = fut.get(unit.getTimeOut(),TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
    // if the task is still running, a TimeOutException will occur while fut.get()
    cat.error("Unit " + unit.getUnitKey() + " Timed Out");
    response.setVote(SCCallOutConsts.TIMEOUT);
} catch (InterruptedException e) {
    cat.error(e);
} catch (ExecutionException e) {
    cat.error(e);
} finally {
    executor.shutdown();
}

在代码中,我应该如何处理InterruptedExceptionExecutionException

这些异常在什么情况下会被抛出?

3个回答

69

ExecutionExceptionInterruptedException是两个非常不同的东西。

ExecutionException会包装执行线程抛出的任何异常,因此,如果您的线程正在执行一些导致抛出IOException的IO操作,该异常将被包装在ExecutionException中并重新抛出。

InterruptedException并不表示出现了任何问题。它存在的目的是为了让您的线程知道何时停止,以使它们可以完成当前的工作并优雅地退出。假设我想让我的应用程序停止运行,但我不想让我的线程在某些操作中途停止(这是如果我将它们设置为守护线程将会发生的事情)。因此,在应用程序关闭时,我的代码调用这些线程上的interrupt方法,这会在它们上设置中断标志,下次这些线程等待或休眠时,它们会检查中断标志并抛出一个InterruptedException,我可以使用它来退出线程正在进行的无限循环处理/休眠逻辑。(而如果线程未等待或休眠,它只需定期检查中断标志即可。)因此,这是使用异常来改变逻辑流程的示例。你只有在示例程序中记录它,或者调试中断逻辑无法正常工作的问题时才需要记录它。


1
有没有办法防止ExecutionException抛出,当任务处理中出现异常时。即使您在处理中捕获了异常并处理它,它仍会将异常包装到ExecutionException中,并在get中抛出。 - G 1
InterruptedException和ExecutionException之间是否存在关系,只是想知道应该先捕获哪一个。在SO问题中,他首先捕获InterruptedException,然后是ExecutionException。如果我们交换顺序,行为会是相同的吗? - prime
@prime: 请参考 https://dev59.com/E2gu5IYBdhLWcg3w8Lev#10964899。这两个异常都扩展自Exception,没有一个比另一个更具体。在这种情况下,顺序并不重要。 - Nathan Hughes

8

如果在计算完成之前等待线程被调用中断,则会抛出InterruptedException

如果计算(在这种情况下是Task)本身抛出异常,则会抛出ExecutionException

如何处理这些异常完全取决于您的应用程序。

编辑:下面是一个被中断的演示:

import java.util.concurrent.*;

public class Test
{
    public static void main(String[] args) throws Exception
    {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<String> future = executor.submit(new SlowCallable());
        executor.submit(new Interruptor(Thread.currentThread()));
        try
        {
            System.out.println(future.get());
        }
        catch (InterruptedException e)
        {
            System.out.println("I was interrupted");
        }
    }

    private static class Interruptor implements Callable<String>
    {
        private final Thread threadToInterrupt;

        Interruptor(Thread threadToInterrupt)
        {
            this.threadToInterrupt = threadToInterrupt;
        }

        public String call() throws Exception
        {
            Thread.sleep(2000);
            threadToInterrupt.interrupt();
            return "interrupted other thread";
        }
    }

    private static class SlowCallable implements Callable<String>
    {
        public String call() throws Exception
        {
            Thread.sleep(5000);
            return "finished";
        }
    }
}

InterruptedException和ExecutionException之间是否存在关联,只是想知道应该先捕获哪一个。在SO问题中,他首先捕获InterruptedException,然后是ExecutionException。如果我们交换顺序,行为会是一样的吗? - prime
@prime:两者都是从Exception派生的,所以交换顺序不会有任何影响。 - Jon Skeet
@JonSkeet 谢谢您的确认。有没有简单的方法来检查两个异常是否相关?或者我们只需要查看它们是否派生自相同的异常即可。 - prime
@prime:确切地说是后者-只需查看文档。或者交换它们的位置,看看代码是否仍然可以编译... - Jon Skeet
@JonSkeet 啊,编译错误。忘记了那个 :) 谢谢。 - prime
显示剩余3条评论

3

返回三种类型的异常的示例代码。

import java.util.concurrent.*;
import java.util.*;

public class ExceptionDemo{
    public static void main(String args[]){
        int poolSize=1;
        int maxPoolSize=1;
        int queueSize=30;
        long aliveTive=60;
        ArrayBlockingQueue<Runnable> queue= new ArrayBlockingQueue<Runnable>(queueSize);
        ThreadPoolExecutor executor= new ThreadPoolExecutor(poolSize,maxPoolSize,aliveTive,
                        TimeUnit.MILLISECONDS,queue);
        List<Future> futures = new ArrayList<Future>();
        for ( int i=0; i < 5; i++){
            futures.add(executor.submit(new RunnableEx()));
        }
        for ( Iterator it = futures.iterator(); it.hasNext();){
            try {
                Future f = (Future)it.next();
                f.get(4000,TimeUnit.MILLISECONDS);
            }catch(TimeoutException terr){
                System.out.println("Timeout exception");
                terr.printStackTrace();
            }
            catch(InterruptedException ierr){
                System.out.println("Interrupted exception:");
                ierr.printStackTrace();
            }catch(ExecutionException err){
                System.out.println("Exeuction exception:");
                err.printStackTrace();
                Thread.currentThread().interrupt();
            }
        }
        executor.shutdown();
    }
}

class RunnableEx implements Runnable{
    public void run() {
        // code in here
        System.out.println("Thread name:"+Thread.currentThread().getName());
        try{
            Random r = new Random();
            if (r.nextInt(2) == 1){
                Thread.sleep(2000);
            }else{
                Thread.sleep(4000);
            }
            System.out.println("eee:"+1/0);
        }catch(InterruptedException irr){
            irr.printStackTrace();
        }
    }
}

输出:

Thread name:pool-1-thread-1
Timeout exception
Thread name:pool-1-thread-1
java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:201)
        at ExceptionDemo.main(ExceptionDemo.java:20)
Thread name:pool-1-thread-1
Exeuction exception:
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:202)
        at ExceptionDemo.main(ExceptionDemo.java:20)
Caused by: java.lang.ArithmeticException: / by zero
        at RunnableEx.run(ExceptionDemo.java:49)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
Interrupted exception:
java.lang.InterruptedException
        at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:400)
        at java.util.concurrent.FutureTask.get(FutureTask.java:199)
        at ExceptionDemo.main(ExceptionDemo.java:20)
Timeout exception
java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:201)
Thread name:pool-1-thread-1
        at ExceptionDemo.main(ExceptionDemo.java:20)
Thread name:pool-1-thread-1
Timeout exception
java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:201)
        at ExceptionDemo.main(ExceptionDemo.java:20)

TimeoutException:当阻塞操作超时时抛出的异常。

在上面的示例中,由于4秒的休眠,一些任务需要更多的时间,并且Future上的get()操作是阻塞的。

要么增加超时时间,要么优化可运行任务。

ExecutionException:在尝试检索由于抛出异常而中止的任务的结果时抛出的异常 => 计算抛出了异常。

在上面的示例中,通过ArithmeticException: / by zero模拟此Exception

通常,如果根本原因是微不足道的(如示例中引用的),则应捕获并修复它。

InterruptedException:当线程等待、睡眠或以其他方式占用时被中断时抛出。

在上面的示例中,通过在ExecutionException期间中断当前线程来模拟此Exception

通常,应捕获它,但不要对其进行操作。


@downvoter,请重新检查问题和答案。这里引用了Java官方文档的原因。 - Ravindra babu
有InterruptedException和ExecutionException之间的关系吗?只是想知道先捕获哪一个。在SO问题中,他首先捕获InterruptedException,然后是ExecutionException。如果我们交换顺序,行为会是一样的吗? - prime
首先应该捕获InterruptedException异常。 - Ravindra babu
有什么想法吗?你能给我一个例子,说明顺序很重要吗? - prime
1
对于上述用例,任何顺序都可以工作,因为它们在层次结构中是不相关的。但如果你先捕获父异常,再捕获子异常,编译器将抛出“异常已经被捕获”的错误。 - Ravindra babu
请问您为什么在ExecutionException的catch块中使用Thread.currentThread().interrupt()? - Jitendra Asawa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接