【发布时间】:2023-04-26 15:57:01
【问题描述】:
与将Runnable 传递给Thread 构造函数的运行线程相比,使用ExecutorService 有什么优势?
【问题讨论】:
标签: java multithreading concurrency executorservice
与将Runnable 传递给Thread 构造函数的运行线程相比,使用ExecutorService 有什么优势?
【问题讨论】:
标签: java multithreading concurrency executorservice
ExecutorService 抽象出许多与较低级别抽象相关的复杂性,例如原始Thread。它提供了安全启动、关闭、提交、执行和阻塞任务成功或突然终止的机制(表示为Runnable 或Callable)。
来自JCiP,第 6.2 节,直接来自马口:
Executor可能是一个简单的接口,但它构成了一个灵活而强大的异步任务执行框架的基础,该框架支持各种任务执行策略。它提供了一种将任务提交与任务执行解耦的标准方法,将任务描述为Runnable。Executor实现还提供生命周期支持和挂钩,用于添加统计信息收集、应用程序管理和监控。 ... 使用Executor通常是在您的应用程序中实现生产者-消费者设计的最简单途径。
j.u.concurrent 框架让您可以将精力集中在构建任务、依赖关系和潜在的并行性上,而不是花费时间(通常是不正确的,并且付出很大的努力)实现并行性的底层基础架构。对于大量并发应用程序,可以直接识别和利用任务边界并利用j.u.c,让您专注于可能需要更专业解决方案的真正并发挑战的小得多的子集。
此外,尽管样板的外观和感觉,Oracle API page summarizing the concurrency utilities 包含一些非常可靠的使用它们的论据,尤其是:
开发人员可能已经 了解标准库 上课,所以不需要学习 ad-hoc 的 API 和行为 并发组件。此外, 并发应用程序远 构建时更易于调试 可靠、经过充分测试的组件。
question on SO 询问一本好书,立即回答是 JCiP。如果您还没有,请给自己一份副本。那里提出的全面的并发方法远远超出了这个问题,从长远来看,它将为您省去很多心痛。
【讨论】:
我看到的一个优势是管理/调度多个线程。使用 ExecutorService,您不必编写自己的线程管理器,这可能会受到错误的困扰。如果您的程序需要一次运行多个线程,这将特别有用。例如你想一次执行两个线程,你可以很容易地这样做:
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(new Runnable() {
public void run() {
System.out.println("Hello world");
}
});
exec.shutdown();
这个例子可能很简单,但请尝试认为“hello world”行包含一个繁重的操作,并且您希望该操作一次在多个线程中运行以提高程序的性能。这只是一个例子,还有很多情况你想调度或运行多个线程并使用 ExecutorService 作为你的线程管理器。
对于运行单个线程,我看不出使用 ExecutorService 有什么明显优势。
【讨论】:
Thread 没有任何意义,因为您只需要Runnable...您甚至都没有启动Thread,所以它只会添加混乱和不必要的包袱。
exec.shutdown(); 始终是一个好习惯。
Executor 框架(内置线程池框架)克服了传统 Thread 的以下限制。
*Exception 异常,因此我们的 JVM 将崩溃。线程池的好处
使用线程池可避免在请求或任务处理期间创建线程,从而缩短响应时间。
使用线程池允许您根据需要更改执行策略。您只需替换 ExecutorService 实现即可从单线程变为多线程。
Java 应用程序中的线程池通过根据系统负载和可用资源创建配置的线程数来提高系统的稳定性。
线程池将应用程序开发人员从线程管理工作中解放出来,并允许专注于业务逻辑。
【讨论】:
以下是一些好处:
【讨论】:
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());
}
【讨论】:
创建一个新线程真的那么贵吗?
作为基准,我刚刚使用 Runnables 和空的 run() 方法创建了 60,000 个线程。创建每个线程后,我立即调用了它的start(..) 方法。这需要大约 30 秒的密集 CPU 活动。针对this question做了类似的实验。总结就是如果线程没有立即结束,大量活动线程累积(几千个),那么就会出现问题:(1)每个线程都有一个堆栈,所以你会耗尽内存, (2) 操作系统对每个进程的线程数可能有限制,但not necessarily, it seems。
所以,据我所知,如果我们谈论的是每秒启动 10 个线程,并且它们都比新线程的启动速度更快,我们可以保证不会超过这个速度,那么 ExecutorService 在可见的性能或稳定性方面没有提供任何具体的优势。 (尽管在代码中表达某些并发想法可能仍然更方便或更易读。)另一方面,如果您可能每秒调度数百或数千个任务,这需要时间来运行,您可能会遇到大问题马上。这可能会意外发生,例如如果您创建线程以响应对服务器的请求,并且您的服务器接收到的请求强度会出现峰值。但是例如一个线程来响应每个用户输入事件(按键、鼠标移动)似乎非常好,只要任务很简短。
【讨论】:
在 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(例如线程)的其他类一起使用
interface Executor
execute(Runnable command)
ExecutorService 接口扩展 Executor 并添加管理方法 - shutdown() 和 submit() 返回 Future[About] - get(), cancel()
interface ExecutorService extends Executor
Future<?> submit(Runnable task)
shutdown()
...
ScheduledExecutorService 扩展 ExecutorService 用于计划执行任务
interface ScheduledExecutorService extends ExecutorService
schedule()
Executors 类是一个工厂,提供 ExecutorService 实现以运行 asynctask[About]
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 来处理异步任务
【讨论】:
在没有最大阈值限制的情况下创建大量线程可能会导致应用程序耗尽堆内存。因此,创建 ThreadPool 是更好的解决方案。使用 ThreadPool 我们可以限制可以池化和重用的线程数。
Executors 框架有助于在 java 中创建线程池。 Executors 类使用 ThreadPoolExecutor 提供了 ExecutorService 的简单实现。
来源:
【讨论】: