在 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
中,而未捕获的 Throwable
s 将绑定到此对象。
当然,我的意思是“是否包含在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如果您不关心返回类型,请使用执行。和提交一样,只是没有Future的返回。
【讨论】:
根据接受的答案,这是不正确的。异常处理是一个非常重要的区别。【参考方案4】:取自 Javadoc:
方法
submit
通过创建和扩展基本方法 @link Executor#execute
返回可用于取消执行和/或等待的 @link Future 完成。
我个人更喜欢使用 execute,因为它感觉更具声明性,尽管这确实是个人喜好问题。
提供更多信息:在ExecutorService
实现的情况下,调用Executors.newSingleThreadedExecutor()
返回的核心实现是ThreadPoolExecutor
。
submit
调用由其父 AbstractExecutorService
提供,所有调用都在内部执行。 execute 被ThreadPoolExecutor
直接覆盖/提供。
【讨论】:
【参考方案5】:完整答案是此处发布的两个答案的组合(加上一点“额外”):
通过提交任务(与执行任务相比),您将获得可用于获取结果或取消操作的未来。execute
时没有这种控制权(因为它的返回类型 id void
)
execute
需要 Runnable
而 submit
可以将 Runnable
或 Callable
作为参数(有关两者之间差异的更多信息 - 见下文)。
execute
会立即冒泡任何未经检查的异常(它不能抛出已检查的异常!!!),而 submit
将 any 类型的异常绑定到作为结果返回的未来,并且仅当您调用future.get()
将引发(包装的)异常。您将获得的 Throwable 是 ExecutionException
的一个实例,如果您调用此对象的 getCause()
,它将返回原始 Throwable。
还有一些(相关的)要点:
即使您想要submit
的任务不需要返回一个
结果,您仍然可以使用Callable<Void>
(而不是使用Runnable
)。
可以使用interrupt 机制取消任务。下面是an example,了解如何实施取消政策
总而言之,将submit
与Callable
一起使用是一种更好的做法(与execute
与Runnable
相比)。我将引用 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