ExecutorService
相比将Runnable
传递到Thread
构造函数中运行线程有什么优势?ExecutorService
相比将Runnable
传递到Thread
构造函数中运行线程有什么优势?ExecutorService
将与底层抽象(如原始 Thread
)相关的许多复杂性进行了抽象。它提供了安全启动、关闭、提交、执行和阻塞任务的机制,任务可以表示为 Runnable
或 Callable
。
出自 JCiP 第6.2节,直接引用:
Executor
可能是一个简单的接口,但它构成了一个灵活和强大的异步任务执行框架的基础,支持广泛的任务执行策略。它提供了一种标准的方法来解耦 任务提交 和 任务执行,将任务描述为Runnable
。Executor
实现还提供了生命周期支持和钩子,用于添加统计信息收集、应用程序管理和监视。 ... 在应用程序中使用Executor
通常是实现生产者-消费者设计的最简单方法。
与其花费时间实现并行基础设施(通常难以正确完成且需要很大的努力),不如使用 j.u.concurrent
框架来专注于结构化任务、依赖关系和潜在并行性。对于大部分并发应用程序,很容易识别和利用任务边界并使用 j.u.c
,这使您可以专注于可能需要更专门的解决方案的更小子集的真正并发挑战。
此外,尽管看起来有些样板代码的感觉,Oracle API 概述并发实用程序页面 包括一些真正坚实的使用它们的理由,其中最重要的是:
开发人员可能已经了解标准库类,因此无需学习临时并行组件的 API 和行为。此外,构建在可靠、经过充分测试的组件上的并发应用程序要简单得多。
Java并发实践
是一本关于并发的好书。如果你还没有阅读过,建议你去获取一份。这本书全面地介绍了并发编程,远超出了这个问题的范畴,而且能够在很长时间内帮助你避免许多不必要的麻烦。
我认为使用ExecutorService的一个优点在于管理/调度多个线程。使用ExecutorService,您不必编写自己的线程管理器,这可以避免出现错误。如果程序需要同时运行多个线程,则特别有用。例如,您想一次执行两个线程,可以像这样轻松完成:
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(new Runnable() {
public void run() {
System.out.println("Hello world");
}
});
exec.shutdown();
这个例子可能很简单,但是试着想象一下,“hello world”行包含了一个复杂的操作,并且你想让该操作在多个线程中同时运行,以提高程序的性能。这只是一个例子,还有许多情况需要安排或运行多个线程并使用ExecutorService作为您的线程管理器。
对于单个线程的运行,我没有看到使用ExecutorService的明显优势。
Runnable
时创建一个Thread
有任何意义......你甚至没有启动该Thread
,所以这只会增加混乱和不必要的负担。 - ColinDexec.shutdown();
是一个很好的实践。 - Aniket ThakurExecutor框架(内置线程池框架)克服了传统Thread的以下限制:
线程池的好处
使用线程池通过避免在请求或任务处理期间创建线程来减少响应时间。
使用线程池允许您根据需要更改执行策略。您只需替换ExecutorService实现,就可以从单个线程转换为多个线程。
Java应用程序中的线程池通过创建一个基于系统负载和可用资源决定的配置线程数来增加系统的稳定性。
线程池使应用程序开发人员摆脱了线程管理的烦恼,并允许专注于业务逻辑。
创建一个新线程真的那么昂贵吗?
作为一个基准测试,我用一个空的run()
方法创建了60,000个带有Runnable
的线程。在创建每个线程后,我立即调用了其start()
方法。这需要大约30秒的强烈CPU活动。针对这个问题已经进行了类似的实验。总结是,如果线程不会立即完成,并且积累了大量的活动线程(几千个),那么就会出现问题:(1)每个线程都有一个堆栈,所以你会耗尽内存;(2)操作系统可能会对每个进程的线程数量设置限制,但似乎并不一定。
因此,据我所知,如果我们说每秒启动10个线程,并且它们都比新线程更快地完成,而且我们可以保证不会超过太多这个速率,那么ExecutorService在可见性能或稳定性方面不提供任何具体优势。(虽然它仍然可以使表达某些并发想法的代码更方便或易读。)另一方面,如果您可能每秒调度数百或数千个需要一段时间才能运行的任务,则可能会立即遇到大问题。这可能会出现意外情况,例如在响应服务器请求时创建线程,并且您的服务器接收到请求强度激增。但是,例如对于每个用户输入事件(按键、鼠标移动)都有一个线程似乎完全没有问题,只要任务简短。
ExecutorService还提供了对FutureTask的访问,一旦后台任务完成,它将返回给调用类。在实现Callable时,这非常有用。
public class TaskOne implements Callable<String> {
@Override
public String call() throws Exception {
String message = "Task One here. . .";
return message;
}
}
public class TaskTwo implements Callable<String> {
@Override
public String call() throws Exception {
String message = "Task Two here . . . ";
return message;
}
}
// from the calling class
ExecutorService service = Executors.newFixedThreadPool(2);
// set of Callable types
Set<Callable<String>>callables = new HashSet<Callable<String>>();
// add tasks to Set
callables.add(new TaskOne());
callables.add(new TaskTwo());
// list of Future<String> types stores the result of invokeAll()
List<Future<String>>futures = service.invokeAll(callables);
// iterate through the list and print results from get();
for(Future<String>future : futures) {
System.out.println(future.get());
}
在Java 1.5版本之前,Thread/Runnable被设计用于两个独立的服务:
ExecutorService通过指定Runnable/Callable作为工作单元和Executor作为执行(带有生命周期)工作单元的机制来解耦这两个服务。
执行器框架
//Task
Runnable someTask = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
//Thread
Thread thread = new Thread(someTask);
thread.start();
//Executor
Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
Thread thread = new Thread(someTask);
thread.start();
}
};
Executor
是一个接口,用于接受 Runnable
。 execute()
方法可以调用 command.run()
或者与其他使用 Runnable
的类(例如 Thread)一起工作。
interface Executor
execute(Runnable command)
ExecutorService
接口扩展了 Executor
并添加了管理方法 - shutdown()
和 submit()
,它返回 Future
[关于] - get()
,cancel()
interface ExecutorService extends Executor
Future<?> submit(Runnable task)
shutdown()
...
ScheduledExecutorService
是 ExecutorService
的扩展,用于计划执行任务。
interface ScheduledExecutorService extends ExecutorService
schedule()
Executors
类是一个工厂,提供 ExecutorService
实现来运行 async
任务[关于]
class Executors
newFixedThreadPool() returns ThreadPoolExecutor
newCachedThreadPool() returns ThreadPoolExecutor
newSingleThreadExecutor() returns FinalizableDelegatedExecutorService
newWorkStealingPool() returns ForkJoinPool
newSingleThreadScheduledExecutor() returns DelegatedScheduledExecutorService
newScheduledThreadPool() returns ScheduledThreadPoolExecutor
...
结论
使用Thread
对CPU和内存来说是一项昂贵的操作。ThreadPoolExecutor
由任务队列(BlockingQueue
)和线程池(一组Worker
)组成,具有更好的性能和处理异步任务的API。
创建大量线程且没有对最大阈值进行限制可能会导致应用程序耗尽堆内存。因此,创建线程池是更好的解决方案。使用线程池,我们可以限制可池化和重复使用的线程数量。
Executors框架简化了在Java中创建线程池的过程。Executors类提供了ThreadPoolExecutor的ExecutorService的简单实现。
来源: