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

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

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

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

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

【问题讨论】:

    标签: java multithreading executorservice


    【解决方案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() 时如何处理这些类型的场景?

    1. 使用 try{} catch{} 块代码嵌入您的任务代码(Runnable 或 Callable 实现)
    2. 实施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 void
      • execute 需要 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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-05-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多