我有一个关于ExecutorService
如何在Java中工作的基本问题。
很难看到简单地创建Threads
以并行执行某些任务和将每个任务分配给ThreadPool
之间的区别。
ExecutorService
看起来也非常简单有效,所以我想知道为什么我们不能一直使用它。
只是一种方式比另一种更快地执行其工作?
这里有两个非常简单的例子来说明两种方式之间的区别:
使用执行程序服务:Hello World(任务)
static class HelloTask implements Runnable {
String msg;
public HelloTask(String msg) {
this.msg = msg;
}
public void run() {
long id = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + id);
}
}
使用执行程序服务:Hello World(创建执行程序,提交)
static class HelloTask {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
for (int i=0; i<ntasks; i++) {
HelloTask t = new HelloTask("Hello from task " + i);
exs.submit(t);
}
exs.shutdown();
}
}
以下是一个类似的示例,但是扩展了Callable接口,你能告诉我两者之间的区别吗?在哪种情况下,应该使用特定的一个而不是另一个?
使用执行者服务:计数器(任务)
static class HelloTaskRet implements Callable<Long> {
String msg;
public HelloTaskRet(String msg) {
this.msg = msg; }
public Long call() {
long tid = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + tid);
return tid;
}
}
使用执行者服务:(创建,提交)
static class HelloTaskRet {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
Future<Long>[] futures = (Future<Long>[]) new Future[ntasks];
for (int i=0; i<ntasks; i++) {
HelloTaskRet t = new HelloTaskRet("Hello from task " + i);
futures[i] = exs.submit(t);
}
exs.shutdown();
}
}
答案 0 :(得分:18)
虽然问题和示例代码没有关联,但我会尝试澄清两者。
ExecutorService
相对于随意生成线程的优点是它可以预测并避免线程创建的开销,这在JVM上相对较大(例如,它需要为每个线程保留内存)。通过可预测性,至少对于fixedThreadPool
,我的意思是你知道最大并发线程数,并且你知道它们何时以及如何被创建(所以你的JVM在突然出现峰值的情况下不会爆炸)。
作者:Vince Emigh:ExecutorService
也支持cachedThreadPool
,但没有。{1}} 最大。人们选择使用ExecutorService
的主要原因是 防止创建多个线程的开销(通过使用 worker) 线程)。它主要用于需要许多小任务的情况 在一个单独的线程上执行。另外,别忘了singleThreadExecutor
。
现在,关于Runnable
vs Callable
的主题,很容易从您的示例中看到。 Callable
可以返回一个值占位符(Future
),最终将由实际值填充。 Runnable
s无法返回任何内容。
作者:Vince Emigh:Runnable
也不能抛出异常,Callable
可以。
答案 1 :(得分:9)
ExecutorService提供了许多优势
即使对于单个线程,我也更喜欢使用Executors.newFixedThreadPool(1);
查看相关的SE问题: