SingleThreadExecutor VS 普通线程

44
除了 Executor 接口具有一些优点(例如管理)之外,执行以下哪种方式是否存在真正的内部差异(大的性能差异,资源消耗等...):
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(runnable);

并且:

Thread thread = new Thread(runnable);
thread.start();

我这里只询问一个线程。


1
在我的问题中,我只询问单个线程执行器。我理解使用多个线程时的优势。 - Magd Kudama
请编辑问题以使其更加明确。 - Magd Kudama
在第二个答案中:“即使是对于单个线程,我也更喜欢使用Executors.newFixedThreadPool(1);”。为什么会这样?这只是因为您正在使用ExecutorService而方便吗?还是有其他原因? - Magd Kudama
@MagdKudama “单线程”并不意味着“单个可运行项”,如果您有多个可运行项,并希望在单个线程中执行它们,则应使用“Executors.newFixedThreadPool(1)”。 - xingbin
显示剩余3条评论
4个回答

30

Executors#newSingleThreadExecutor() 在背后创建了一个 ThreadPoolExecutor 对象,
可以在这里查看代码:http://www.docjar.com/html/api/java/util/concurrent/Executors.java.html

  133       public static ExecutorService newSingleThreadExecutor() {
  134           return new FinalizableDelegatedExecutorService
  135               (new ThreadPoolExecutor(1, 1,
  136                                       0L, TimeUnit.MILLISECONDS,
  137                                       new LinkedBlockingQueue<Runnable>()));
  138       }

ThreadPoolExecutor 的文档 解释了在什么情况下它会带来好处:

线程池解决了两个不同的问题:当执行大量异步任务时,由于减少了每个任务调用的开销,它们通常提供更好的性能,并且它们提供了一种绑定和管理资源(包括线程)的方法,在执行任务集合时消耗这些资源。每个 ThreadPoolExecutor 还会维护一些基本统计信息,例如完成的任务数。

如果你只需要偶尔运行单个线程(例如每小时一次),那么就性能而言,使用 ThreadPoolExecutor 可能会更慢,因为你需要实例化整个机制(池+线程),然后将其从内存中丢弃。

但是,如果您经常使用此单个线程(例如每 15 秒钟一次),则优点在于您只需创建一次池和线程,将其保存在内存中,并在所有时间内使用它,节省了每隔一段时间创建新线程的时间(如果您想要每 15 秒钟使用它,则可能相当昂贵)。


1
正如我所问的关于性能差异以及是否有遗漏的地方,我认为这是正确的答案。总体来说是非常好的回答。谢谢。 - Magd Kudama
1
拥有ExecutorService/ThreadPoolExecutor的另一个优点是它允许您提交任务并获取Future。如果您加入了Future,则可以确保没有未捕获的异常。对于普通的Thread,您需要注意未捕获的异常,并可能希望使用Thread.setUncaughtExceptionHandler - cambunctious

11

主要的区别在于任务执行策略。

创建一个Thread实例或者继承Thread类,基本上是执行单个任务。

而另一方面使用Executors.newSingleThreadExecutor()允许您提交多个任务。由于这些任务保证不会并发执行,因此可以利用以下线程封闭(thread confinement)的优势:

  • 访问不安全的对象时无需同步
  • 一个任务的内存效果保证对下一个任务可见


4

这是一个抽象概念,但这种抽象常常伴随着一定的“代价”:

  • 可能会有一些性能损失
  • 控制力度会降低(这也是整个抽象的目的——你无需处理底层细节,如果必要,...)

主要区别在于该服务使您能够提交多个任务,而线程只能运行一个Runnable。另一方面,您需要担心的事情就是“关闭”服务之类的问题。

一个经验法则:在此处应该将性能方面的影响视为“可以忽略不计”。因此,您更喜欢“更抽象”的执行器服务解决方案。因为这样可以将您的关注点与实际线程分离开来。更重要的是:如果您选择使用不同的服务实现方式... 您的其余代码不需要关心这个问题。

长话短说:抽象概念固然有代价,但在这种情况下,您通常更喜欢“更抽象”的解决方案。因为最终这可以降低您的解决方案的复杂性。


3
如果您只需要执行一个Runnable,那么它们之间没有太大的区别。
使用普通线程可能会更有效率,因为创建ExecutorService(如ThreadPoolExecutor)除了创建新线程外还要做一些其他事情,例如创建阻塞队列、创建策略,尽管这些都是隐式完成的。
此外,当这个Runnable执行完毕后,必须关闭执行器。否则,池中的单个线程将永远无法退出。

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