在 ExecutorService 的提交和 ExecutorService 的执行之间进行选择

Posted

技术标签:

【中文标题】在 ExecutorService 的提交和 ExecutorService 的执行之间进行选择【英文标题】:Choose between ExecutorService's submit and ExecutorService's execute 【发布时间】:2011-04-25 04:00:52 【问题描述】:

如果我不关心返回值,我应该如何在 ExecutorService 的 submit 或 execute 之间进行选择?

如果我同时测试两者,除了返回值之外,我没有看到两者之间的任何差异。

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.execute(new Task());

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.submit(new Task());

【问题讨论】:

【参考方案1】:

在异常/错误处理方面存在差异。

使用execute() 排队的任务生成一些Throwable 将导致UncaughtExceptionHandler 运行该任务的Thread 被调用。如果未安装自定义处理程序,将调用默认的 UncaughtExceptionHandler,通常将 Throwable 堆栈跟踪打印到 System.err

另一方面,由使用submit() 排队的任务生成的Throwable 会将Throwable 绑定到由对submit() 的调用产生的Future。在该Future 上调用get() 将引发ExecutionException,其原因是原始Throwable(可通过在ExecutionException 上调用getCause() 访问)。

【讨论】:

请注意,此行为无法保证,因为它取决于您的 Runnable 是否包含在 Task 中,您可能无法控制。例如,如果您的 Executor 实际上是 ScheduledExecutorService,则您的任务将在内部包裹在 Future 中,而未捕获的 Throwables 将绑定到此对象。 当然,我的意思是“是否包含在Future 中”。例如,请参阅 ScheduledThreadPoolExecutor#execute 的 Javadoc。 那么执行器服务中的线程会发生什么?例如:如果我们有固定的线程执行器有 10 个数字并且抛出异常,是否会替换一个新线程并且仍然有 10 个线程?在这种情况下提交和执行之间还有什么区别吗?【参考方案2】:

执行:使用它来触发并忘记调用

提交:使用它来检查方法调用的结果并对调用返回的Future对象采取适当的行动

来自javadocs

submit(Callable<T> task)

提交一个返回值的任务执行并返回一个Future 表示任务的待处理结果。

Future<?> submit(Runnable task)

提交一个 Runnable 任务以供执行,并返回一个代表该任务的 Future 任务。

void execute(Runnable command)

在未来的某个时间执行给定的命令。该命令可以在新线程、池线程或调用线程中执行,由 Executor 实现自行决定。

您在使用submit() 时必须采取预防措施。除非您将任务代码嵌入到 try catch 块中,否则它会在框架本身中隐藏异常。

示例代码:此代码吞并Arithmetic exception : / by zero

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

public class ExecuteSubmitDemo
    public ExecuteSubmitDemo()
    
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(10);
        //ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable()
                 public void run()
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 
            );
        service.shutdown();
    
    public static void main(String args[])
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    

输出:

java ExecuteSubmitDemo
creating service
a and b=4:0

submit() 替换为execute() 会引发相同的代码:

替换

service.submit(new Runnable()

service.execute(new Runnable()

输出:

java ExecuteSubmitDemo
creating service
a and b=4:0
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
        at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:14)
        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)

在使用 submit() 时如何处理这些类型的场景?

    使用 try catch 块代码嵌入您的任务代码(Runnable 或 Callable 实现) 实施CustomThreadPoolExecutor

新解决方案:

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

public class ExecuteSubmitDemo
    public ExecuteSubmitDemo()
    
        System.out.println("creating service");
        //ExecutorService service = Executors.newFixedThreadPool(10);
        ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable()
                 public void run()
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 
            );
        service.shutdown();
    
    public static void main(String args[])
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    


class ExtendedExecutor extends ThreadPoolExecutor 

   public ExtendedExecutor()  
       super(1,1,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
   
   // ...
   protected void afterExecute(Runnable r, Throwable t) 
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) 
       try 
         Object result = ((Future<?>) r).get();
        catch (CancellationException ce) 
           t = ce;
        catch (ExecutionException ee) 
           t = ee.getCause();
        catch (InterruptedException ie) 
           Thread.currentThread().interrupt(); // ignore/reset
       
     
     if (t != null)
       System.out.println(t);
   
 

输出:

java ExecuteSubmitDemo
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero

【讨论】:

很好的清晰解释。虽然扩展它并不是真正需要的。只是必须消耗未来的对象才能知道任务是否成功。因此,如果您打算使用 Future,请使用 submit(),否则只需使用 execute()【参考方案3】:

如果您不关心返回类型,请使用执行。和提交一样,只是没有Future的返回。

【讨论】:

根据接受的答案,这是不正确的。异常处理是一个非常重要的区别。【参考方案4】:

取自 Javadoc:

方法 submit 通过创建和扩展基本方法 @link Executor#execute 返回可用于取消执行和/或等待的 @link Future 完成。

我个人更喜欢使用 execute,因为它感觉更具声明性,尽管这确实是个人喜好问题。

提供更多信息:在ExecutorService 实现的情况下,调用Executors.newSingleThreadedExecutor() 返回的核心实现是ThreadPoolExecutor

submit 调用由其父 AbstractExecutorService 提供,所有调用都在内部执行。 execute 被ThreadPoolExecutor 直接覆盖/提供。

【讨论】:

【参考方案5】:

完整答案是此处发布的两个答案的组合(加上一点“额外”):

通过提交任务(与执行任务相比),您将获得可用于获取结果或取消操作的未来。 execute 时没有这种控制权(因为它的返回类型 id voidexecute 需要 Runnablesubmit 可以将 RunnableCallable 作为参数(有关两者之间差异的更多信息 - 见下文)。 execute 会立即冒泡任何未经检查的异常(它不能抛出已检查的异常!!!),而 submitany 类型的异常绑定到作为结果返回的未来,并且仅当您调用future.get() 将引发(包装的)异常。您将获得的 Throwable 是 ExecutionException 的一个实例,如果您调用此对象的 getCause(),它将返回原始 Throwable。

还有一些(相关的)要点:

即使您想要submit 的任务不需要返回一个 结果,您仍然可以使用Callable&lt;Void&gt;(而不是使用Runnable)。 可以使用interrupt 机制取消任务。下面是an example,了解如何实施取消政策

总而言之,将submitCallable 一起使用是一种更好的做法(与executeRunnable 相比)。我将引用 Brian Goetz 的“Java concurrency in practice”:

6.3.2 有结果的任务:Callable 和 Future

Executor 框架使用 Runnable 作为其基本任务表示。 Runnable 是一个相当 限制抽象;运行不能返回值或抛出检查 例外,尽管它可能会产生副作用,例如写入日志 文件或将结果放在共享数据结构中。许多任务是 有效地延迟计算——执行数据库查询,获取 网络上的资源,或计算复杂的功能。为了 这些类型的任务,Callable 是一个更好的抽象:它期望 主入口点 call 将返回一个值并预期 它可能会引发异常。7 Executors 包括几个实用程序 包装其他类型任务的方法,包括 Runnable 和 java.security.PrivilegedAction,带有 Callable。

【讨论】:

【参考方案6】:

来自Javadoc:

根据 Executor 实现的判断,该命令可以在新线程、池线程或调用线程中执行。

因此,根据Executor 的实现,您可能会发现提交线程在任务执行时会阻塞。

【讨论】:

【参考方案7】:

只是添加到接受的答案-

但是,从任务中抛出的异常会使其未被捕获 仅针对使用 execute() 提交的任务的异常处理程序;对于任务 使用 submit() 提交给执行器服务,任何抛出的异常 被认为是任务返回状态的一部分。

Source

【讨论】:

以上是关于在 ExecutorService 的提交和 ExecutorService 的执行之间进行选择的主要内容,如果未能解决你的问题,请参考以下文章

ExecutorService——shutdown方法和awaitTermination方法

ExecutorService对象的shutdown 和shutdownNow 的区别

Java:在特定队列大小后阻止提交的 ExecutorService [重复]

Java多线程工具包java.util.concurrent---ExecutorService

ExecutorService的shutdown到底什么时候关闭

java中ExecutorService的线程池如何暂停所有的任务和继续所有的任务? 有这样的函数吗?