在Java中,ExecutorService.submit和ExecutorService.execute在这段代码中有什么区别?

114

我正在学习使用ExectorService来池化threads并发送任务。我有一个简单的程序如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


class Processor implements Runnable {

    private int id;

    public Processor(int id) {
        this.id = id;
    }

    public void run() {
        System.out.println("Starting: " + id);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println("sorry, being interupted, good bye!");
            System.out.println("Interrupted " + Thread.currentThread().getName());
            e.printStackTrace();
        }

        System.out.println("Completed: " + id);
    }
}


public class ExecutorExample {

    public static void main(String[] args) {
        Boolean isCompleted = false;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 5; i++) {
            executor.execute(new Processor(i));
        }

        //executor does not accept any more tasks but the submitted tasks continue
        executor.shutdown();

        System.out.println("All tasks submitted.");

        try {
            //wait for the exectutor to terminate normally, which will return true
            //if timeout happens, returns false, but this does NOT interrupt the threads
            isCompleted = executor.awaitTermination(100, TimeUnit.SECONDS);
            //this will interrupt thread it manages. catch the interrupted exception in the threads
            //If not, threads will run forever and executor will never be able to shutdown.
            executor.shutdownNow();
        } catch (InterruptedException e) {
        }

        if (isCompleted) {
            System.out.println("All tasks completed.");
        } else {
            System.out.println("Timeout " + Thread.currentThread().getName());
        }
    }
}
它没有任何花哨的功能,但创建了两个线程并总共提交了5个任务。每个线程完成其任务后,它会接下来执行下一个任务。 在上面的代码中,我使用了executor.submit。我还改为了executor.execute。但我没有看到输出中有任何区别。submitexecute方法有什么不同? 这是API所说的内容:
"方法submit通过创建和返回可以用于取消执行和/或等待完成的Future,扩展了基本方法Executor.execute(java.lang.Runnable)。方法invokeAny和invokeAll执行最常用的批量执行形式,即执行一组任务,然后等待至少一个或全部完成。(类ExecutorCompletionService可用于编写这些方法的自定义变体。)"
但是我不清楚它确切的含义是什么?
9个回答

92

从JavaDoc中可以看出,execute(Runnable)方法不返回任何内容。

然而,submit(Callable<T>)方法返回一个Future对象,它允许您以编程方式取消正在运行的线程,并在Callable完成时获取返回的T。有关详细信息,请参阅Future的JavaDoc

Future<?> future = executor.submit(longRunningJob);
...
//long running job is taking too long
future.cancel(true);

此外,如果future.get() == null且没有抛出任何异常,则表明Runnable执行成功。

2
只是补充一下-> 一旦线程接收到任务,就无法取消。虽然它会中断正在运行的线程。 - Shanu Gupta
1
另一个区别是,如果使用 execute(...) 运行的作业抛出异常,则线程将在此过程中被终止,然后可能由 ExecutorService 重新启动。 - Gray

59

execute的区别在于,它只是简单地启动任务,而submit则返回一个Future对象来管理任务。您可以使用Future对象执行以下操作:

  • 使用cancel方法提前取消任务。
  • 使用get等待任务执行完成。

如果将Callable提交到池中,则Future接口更加有用。当您调用Future.get时,call方法的返回值将被返回。如果您不维护对Future的引用,则没有任何区别。


7
不,execute() 规范说明“在将来的某个时间执行给定命令”,因此它不一定会立即启动。区别在于你不关心何时执行或检查结果或完成情况。 - zakmck

38

execute: 用于“发射并忘记”调用

submit: 用于取消执行和/或等待创建的Future对象完成

主要区别:异常处理

submit()在框架本身中隐藏未处理的Exception

execute()抛出未处理的Exception

使用submit()处理异常的解决方案:

  1. 将您的Callable或Runnable代码包装在try{} catch{}块中

    或者

  2. 在try{} catch{}块中保留future.get()调用

    或者

  3. 实现自己的ThreadPoolExecutor并覆盖afterExecute方法

相关帖子及代码示例:

选择ExecutorService的submit和ExecutorService的execute之间的区别

Future.get() 将您的异步执行转换为同步执行,您将无法获得异步执行的好处。

如果您的业务用例围绕异步事件处理,并且您期望在 X 秒内响应,请在 Future 操作上设置超时。

关于您其他的查询:

invokeAll:

执行给定的任务,返回一个 Future 列表,其中包含它们的状态和结果,当所有任务完成或超时到期时,以先发生的为准。

invokeAny:

执行给定的任务,如果有任何一个成功完成(即没有抛出异常),则返回其结果,在给定的超时时间之前。

如果您想等待所有提交的任务完成,请使用 invokeAll

如果您希望在N个提交的任务中成功完成一个任务,可以使用invokeAny。在这种情况下,如果其中一个任务成功完成,正在进行的任务将被取消。


3
submit does not "hide" an exception, it throws it wrapped in ExecutionException when get is called. The original exception can then be retrieved by ExecutionException.getCause() - user5818995
精确并且简洁的回答,@Ravindra 得满分。 - Haseeb

10
submit()和execute()方法的主要区别在于,ExecuterService.submit()可以返回计算结果,因为它具有Future类型的返回值,而execute()方法不返回任何结果,因为其返回类型是void。Java 1.5 Executor框架中的核心接口是Executor接口,它定义了execute(Runnable task)方法,其主要目的是将任务与其执行分离。
提交给Executor的任何任务都可以由同一个线程、线程池中的工作线程或任何其他线程执行。
另一方面,submit()方法定义在ExecutorService接口中,它是Executor的子接口,除了添加可以接受Callable任务并返回计算结果的submit()方法外,还添加了终止线程池的功能。
execute()和submit()的相似点包括:
1. submit()和execute()方法都用于将任务提交到Executor框架进行异步执行。 2. submit()和execute()都可以接受Runnable任务。 3. 您可以从ExecutorService接口访问submit()和execute(),因为它还扩展了声明execute()方法的Executor接口。

除了submit()方法可以返回输出并且execute()不能,以下是Java 5 Executor框架这两种关键方法的其他显著区别:

  1. submit()方法可以接受Runnable和Callable任务,而execute()方法只能接受Runnable任务。
  2. submit()方法在ExecutorService接口中声明,而execute()方法在Executor接口中声明。
  3. submit()方法的返回类型是Future对象,而execute()方法的返回类型是void。

7

如果您查看源代码,您会发现submit有点类似于对execute的包装

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

5

提交 - 返回Future对象,可用于检查已提交任务的结果。可以用于取消或检查isDone等。

执行 - 不返回任何内容。


1

除了之前的回答外,即:

  • execute(..)运行任务并忘记它
  • submit(...)返回一个future;

future的主要优点是你可以建立超时。如果您有一个带有有限线程数的执行程序,并且您的执行需要很长时间,这将非常有用,它不会挂起进程。

示例1:永远挂起并占满执行器

  ExecutorService executor = Executors.newFixedThreadPool(2);
  for (int i=0; i < 5; i++) {
     executor.execute(() -> {
         while (true) {
           System.out.println("Running...")
           Thread.sleep(Long.MAX_VALUE)
         }              
     });
  }

你的输出将是(即只有2并且它会停止):
Running...
Running...

另一方面,您可以使用submit并添加超时:
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i=0; i < 5; i++) {
    Future future = executor.submit(() -> {
        while (true) {
            System.out.println("Running...");
            Thread.sleep(Long.MAX_VALUE);
        }
    });

    try {
        future.get(1, TimeUnit.SECONDS);
    } catch (Exception e) {
        if (!future.isDone()) {
            System.out.println("Oops: " + e.getClass().getSimpleName());
            future.cancel(true);
        }
    }
}

输出结果将会像这样(注意执行器不会被卡住,但你需要手动取消未来的执行):
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException

1
execute(Runnable command) 是从接口Executor实现的方法。它的意思是只执行命令,不返回任何结果。 ExecutorService 有其自己的启动任务的方法:submitinvokeAnyinvokeAll,它们的主要目标都是 Callable 实例。虽然有一些输入为 Runnable 的方法,但实际上在方法中 Runnable 将被适配为 Callable。为什么使用 Callable?因为我们可以在任务提交后获得一个 Future<T> 结果。
但是当你将 Runnable 转换为 Callable 时,你得到的结果只是你传递的值:
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

因为有一个只有Runnable作为参数而没有特定结果的方法,所以我们将Runnable传递给submit而不是在任务完成时仅获取结果的意义是什么?请阅读Future的javadoc:
如果您希望出于可取消性而使用Future但不提供可用结果,则可以声明形式为Future<?>的类型,并将null作为底层任务的结果返回。
因此,如果您只想执行一个没有返回值的Runnable任务,则可以使用execute()。如果要运行一个Callable任务,或者如果要运行一个具有指定结果作为完成符号的Runnable任务,或者如果要运行一个任务并具有取消它的能力,则应使用submit()

0
基本上,两个调用都会执行。如果你想要 Future 对象,你应该从文档中调用 submit() 方法。
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}


public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

正如你所看到的,Java 真的没有其他方法来启动线程,除了调用 run() 方法。在我看来,因为我也发现 Callable.call() 方法是在 run() 方法内部被调用的。因此,如果对象是可调用的,它仍然会调用 run() 方法,这将进而调用 call() 方法。

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

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