ExecutorService与Casual Thread Spawner

我有一个关于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 { 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[] futures = (Future[]) 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(); } } 

虽然问题和示例代码没有关联,但我会尝试澄清两者。 ExecutorService相对于随意生成线程的优势在于它的行为可预测并且避免了线程创建的开销,这在JVM上相对较大(例如,它需要为每个线程保留内存)。 通过可预测性,至少对于fixedThreadPool ,我的意思是你知道最大并发线程数,并且你知道它们何时以及如何被创建(所以你的JVM在突然出现峰值的情况下不会爆炸)。

作者:Vince Emigh: ExecutorService还支持cachedThreadPool ,它没有最大值。 人们选择使用ExecutorService主要原因是为了防止创建多个线程的开销(通过使用工作线程 )。 它主要用于需要在单独的线程上执行许多小任务的情况。 另外,不要忘记singleThreadExecutor

现在,关于Runnable vs Callable的主题,很容易从您的示例中看到。 Callable可以返回一个值占位符( Future ),最终将由实际值填充。 Runnable s无法返回任何内容。

作者:Vince Emigh: Runnable也不能抛出exception,而Callable可以。

与普通线程相比, ExecutorService提供了许多优势

  1. 您可以创建/管理/控制线程的生命周期并优化线程创建成本开销
  2. 您可以控制任务的处理 (工作窃取,ForkJoinPool,invokeAll)等。
  3. 您可以在将来的时间安排任务
  4. 您可以监视线程的进度和运行状况

即使是单个Thread,我也更喜欢使用Executors.newFixedThreadPool(1);

看看相关的SE问题:

Java的Fork / Join vs ExecutorService – 何时使用哪个?

使用ExecutorService有什么好处?