如果返回值与我无关,我应该如何在ExecutorService的提交或执行之间做出选择?

如果我对两者都进行测试,除了返回值之外,我没有看到两者之间有任何差异。

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

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

摘自Javadoc:

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

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

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

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


如果你不关心返回类型,使用execute。它和submit一样,只是没有Future的返回。


在异常/错误处理方面有区别。

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

另一方面,由使用submit()排队的任务生成的Throwable将绑定到调用submit()生成的Future。在该Future上调用get()将抛出一个ExecutionException,其原因为原始Throwable(通过在ExecutionException上调用getCause()来访问)。


来自Javadoc:

该命令可以在新线程、池线程或调用线程中执行,由Executor实现自行决定。

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


execute:用于火灾和忘记呼叫

submit:使用它来检查方法调用的结果,并对调用返回的Future对象采取适当的操作

从javadoc

提交(调用< T >任务)

提交一个值返回任务以执行并返回一个Future 表示任务的挂起结果。

未来的< ?> submit(可运行任务)

提交一个可运行任务以供执行,并返回表示该任务的Future 的任务。

void execute(Runnable command)

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

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

算术异常:/ by 0。

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

用execute()替换submit()会抛出相同的代码:

取代

service.submit(new Runnable(){

with

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

完整的答案是在这里发表的两个答案的组合(加上一点“额外的”):

By submitting a task (vs. executing it) you get back a future which can be used to get the result or cancel the action. You don't have this kind of control when you execute (because its return type id void) execute expects a Runnable while submit can take either a Runnable or a Callable as an argument (for more info about the difference between the two - see below). execute bubbles up any unchecked-exceptions right away (it cannot throw checked exceptions!!!), while submit binds any kind of exception to the future that returns as a result, and only when you call future.get() a the (wrapped) exception will be thrown . The Throwable that you'll get is an instance of ExecutionException and if you'll call this object's getCause() it will return the original Throwable.

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

即使要提交的任务不需要返回 结果,你仍然可以使用Callable<Void>(而不是使用Runnable)。 可以使用中断机制来取消任务。下面是如何实现取消策略的示例

总而言之,在Callable对象中使用submit(相对于在Runnable对象中使用execute)是一个更好的实践。我将引用Brian Goetz的《Java并发实践》:

6.3.2 Result-bearing tasks: Callable and Future The Executor framework uses Runnable as its basic task representation. Runnable is a fairly limiting abstraction; run cannot return a value or throw checked exceptions, although it can have side effects such as writing to a log file or placing a result in a shared data structure. Many tasks are effectively deferred computations—executing a database query, fetching a resource over the network, or computing a complicated function. For these types of tasks, Callable is a better abstraction: it expects that the main entry point, call, will return a value and anticipates that it might throw an exception.7 Executors includes several utility methods for wrapping other types of tasks, including Runnable and java.security.PrivilegedAction, with a Callable.


只是在公认的答案上加上

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