在 Java 的这段代码中,ExecutorService.commit 和 ExecutorService.execute 之间的区别是什么?

我正在学习使用 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());
}
}
}

它没有什么特别之处,但是创建了两个 threads并且总共提交了5个任务。在每个 thread完成它的任务之后,它接受下一个任务, 在上面的代码中,我使用 executor.submit。我也换成了 executor.execute。但是我没有看到任何输出上的差异。submitexecute的方法在哪些方面不同? API是这么说的

方法提交扩展了基本方法 Executor.execute (java.lang。通过创建并返回一个 Future,该 Future 可用于取消执行和/或等待完成。方法 invkeAnyandinvkeAll 执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部任务完成。(类 ExecutorCompletionService 可用于编写这些方法的自定义变体。)

但我不清楚它到底是什么意思?

117142 次浏览

The difference is that execute simply starts the task without any further ado, whereas submit returns a Future object to manage the task. You can do the following things with the Future object:

  • Cancel the task prematurely, with the cancel method.
  • Wait for the task to finish executing, with get.

The Future interface is more useful if you submit a Callable to the pool. The return value of the call method will be returned when you call Future.get. If you don't maintain a reference to the Future, there is no difference.

As you see from the JavaDoc execute(Runnable) does not return anything.

However, submit(Callable<T>) returns a Future object which allows a way for you to programatically cancel the running thread later as well as get the T that is returned when the Callable completes. See JavaDoc of Future for more details

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

Moreover, if future.get() == null and doesn't throw any exception then Runnable executed successfully

Submit - Returns Future object, which can be used to check result of submitted task. Can be used to cancel or to check isDone etc.

Execute - doesn't return anything.

execute: Use it for fire and forget calls

submit: Use it to inspect the result of method call and take appropriate action on Future objected returned by the call

Major difference: Exception handling

submit() hides un-handled Exception in framework itself.

execute() throws un-handled Exception.

Solution for handling Exceptions with submit()

  1. Wrap your Callable or Runnable code in try{} catch{} block

    OR

  2. Keep future.get() call in try{} catch{} block

    OR

  3. implement your own ThreadPoolExecutor and override afterExecute method

Regarding tour other queries on

invokeAll:

Executes the given tasks, returning a list of Futures holding their status and results when all complete or the timeout expires, whichever happens first.

invokeAny:

Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do before the given timeout elapses.

Use invokeAll if you want to wait for all submitted tasks to complete.

Use invokeAny if you are looking for successful completion of one task out of N submitted tasks. In this case, tasks in progress will be cancelled if one of the tasks completes successfully.

Related post with code example:

Choose between ExecutorService's submit and ExecutorService's execute

basically both calls execute,if u want future object you shall call submit() method here from the doc

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;
}

as you can see java really has no way to start a thread other than calling run() method, IMO. since i also found that Callable.call() method is called inside run() method. hence if the object is callable it would still call run() method, which inturn would call call() method from doc.

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);
}
}

A main difference between the submit() and execute() method is that ExecuterService.submit()can return result of computation because it has a return type of Future, but execute() method cannot return anything because it's return type is void. The core interface in Java 1.5's Executor framework is the Executor interface which defines the execute(Runnable task) method, whose primary purpose is to separate the task from its execution.

Any task submitted to Executor can be executed by the same thread, a worker thread from a thread pool or any other thread.

On the other hand, submit() method is defined in the ExecutorService interface which is a sub-interface of Executor and adds the functionality of terminating the thread pool, along with adding submit() method which can accept a Callable task and return a result of computation.

Similarities between the execute() and submit() as well:

  1. Both submit() and execute() methods are used to submit a task to Executor framework for asynchronous execution.
  2. Both submit() and execute() can accept a Runnable task.
  3. You can access submit() and execute() from the ExecutorService interface because it also extends the Executor interface which declares the execute() method.

Apart from the fact that submit() method can return output and execute() cannot, following are other notable differences between these two key methods of Executor framework of Java 5.

  1. The submit() can accept both Runnable and Callable task but execute() can only accept the Runnable task.
  2. The submit() method is declared in ExecutorService interface while execute() method is declared in the Executor interface.
  3. The return type of submit() method is a Future object but return type of execute() method is void.

If you check the source code, you will see that submit is sort of a wrapper on execute

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

The execute(Runnable command) is the implemented method from Interface Executor. It means just execute the command and gets nothing returned.

ExecutorService has its own methods for starting tasks: submit, invokeAny and invokeAll all of which have Callable instances as their main targets. Though there're methods having Runnable as input, actulaly Runnable will be adapted to Callable in the method. why Callable? Because we can get a Future<T> result after the task is submitted.

But when you transform a Runnable to a Callable, result you get is just the value you pass:

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;
}
}

So, what's the point that we pass a Runnable to submit instead of just getting the result when the task is finished? Because there's a method which has only Runnable as parameter without a particular result.

Read the javadoc of Future:

If you would like to use a Future for the sake of cancellability but not provide a usable result, you can declare types of the form Future<?> and return null as a result of the underlying task.

So, if you just want to execute a Runnable task without any value returned, you can use execute().

if you want to run a Callable task, or

if you want to run a Runnable task with a specified result as the completion symbol, or

if you want to run a task and have the ability to cancel it,

you should use submit().

On top of previous responses, i.e.

  • execute(..) runs the task and forget about it
  • submit(...) returns a future;

The main advantage with the future is that you can establish a timeout. This might come very handy if you have an executor with a limited number of threads and your executions are taking forever, it will not hang the process.

Example 1: hangs forever and fills the executor

  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)
}
});
}

Your output will be (i.e. only 2 and it gets stuck):

Running...
Running...

On the other hand, you can use submit and add a timeout:

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);
}
}
}

The output will look like this (notice that the executor does not get stuck, but you need to manually cancel the future):

Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException
Running...
Oops: TimeoutException