使用 ExecutorService 有啥好处?

Posted

技术标签:

【中文标题】使用 ExecutorService 有啥好处?【英文标题】:What are the advantages of using an ExecutorService?使用 ExecutorService 有什么好处? 【发布时间】:2011-04-28 09:33:15 【问题描述】:

与将Runnable 传递给Thread 构造函数的运行线程相比,使用ExecutorService 有什么优势?

【问题讨论】:

【参考方案1】:

ExecutorService 抽象出许多与较低级别抽象相关的复杂性,例如原始Thread。它提供了安全启动、关闭、提交、执行和阻塞任务成功或突然终止的机制(表示为RunnableCallable)。

来自JCiP,第 6.2 节,直接来自马口:

Executor 可能是一个简单的接口,但它构成了一个灵活而强大的异步任务执行框架的基础,该框架支持各种任务执行策略。它提供了一种将任务提交任务执行解耦的标准方法,将任务描述为RunnableExecutor 实现还提供生命周期支持和挂钩,用于添加统计信息收集、应用程序管理和监控。 ... 使用Executor 通常是在您的应用程序中实现生产者-消费者设计的最简单途径。

j.u.concurrent 框架让您可以将精力集中在构建任务、依赖关系和潜在的并行性上,而不是花费时间(通常是不正确的,并且付出很大的努力)实现并行性的底层基础架构。对于大量并发应用程序,可以直接识别和利用任务边界并利用j.u.c,让您专注于可能需要更专业解决方案的真正并发挑战的小得多的子集。

此外,尽管样板的外观和感觉,Oracle API page summarizing the concurrency utilities 包含一些非常可靠的使用它们的论据,尤其是:

开发人员可能已经 了解标准库 上课,所以不需要学习 ad-hoc 的 API 和行为 并发组件。此外, 并发应用程序远 构建时更易于调试 可靠、经过充分测试的组件。

question on SO 询问一本好书,立即回答是 JCiP。如果您还没有,请给自己一份副本。那里提出的全面的并发方法远远超出了这个问题,从长远来看,它将为您省去很多心痛。

【讨论】:

java中Thread类上下文中的“任务提交与任务执行解耦”是什么意思【参考方案2】:

我看到的一个优势是管理/调度多个线程。使用 ExecutorService,您不必编写自己的线程管理器,这可能会受到错误的困扰。如果您的程序需要一次运行多个线程,这将特别有用。例如你想一次执行两个线程,你可以很容易地这样做:

ExecutorService exec = Executors.newFixedThreadPool(2);

exec.execute(new Runnable() 
  public void run() 
    System.out.println("Hello world");
  
);

exec.shutdown();

这个例子可能很简单,但请尝试认为“hello world”行包含一个繁重的操作,并且您希望该操作一次在多个线程中运行以提高程序的性能。这只是一个例子,还有很多情况你想调度或运行多个线程并使用 ExecutorService 作为你的线程管理器。

对于运行单个线程,我看不出使用 ExecutorService 有什么明显优势。

【讨论】:

不就是exec.execute(new Runnable().. ? 两者都可以,因为 Thread 实现了 Runnable。对于简单的情况,Runnable 应该足够了。 我真的认为创建Thread 没有任何意义,因为您只需要Runnable...您甚至都没有启动Thread,所以它只会添加混乱和不必要的包袱。 ayt,已应用评论评论。 :) 在 finally 语句中使用空检查执行 exec.shutdown(); 始终是一个好习惯。【参考方案3】:

Executor 框架(内置线程池框架)克服了传统 Thread 的以下限制。

资源管理不善,即它不断为每个请求创建新资源。创建资源没有限制。使用 Executor 框架,我们可以重用现有资源并限制创建资源。 Not Robust:如果我们继续创建新线程,我们将收到***Exception 异常,因此我们的 JVM 将崩溃。 开销创建时间:对于每个请求,我们都需要创建新资源。创建新资源非常耗时。即线程创建>任务。使用 Executor 框架,我们可以在线程池中构建。

线程池的好处

使用线程池可避免在请求或任务处理期间创建线程,从而缩短响应时间。

使用线程池允许您根据需要更改执行策略。您只需替换 ExecutorService 实现即可从单线程变为多线程。

Java 应用程序中的线程池通过根据系统负载和可用资源创建配置的线程数来提高系统的稳定性。

线程池将应用程序开发人员从线程管理工作中解放出来,并允许专注于业务逻辑。

Source

【讨论】:

【参考方案4】:

以下是一些好处:

    Executor 服务以异步方式管理线程 使用Future callable获取线程完成后的返回结果。 管理工作分配以释放线程并从线程转售已完成的工作以自动分配新工作 fork - 用于并行处理的连接框架 更好的线程间通信 invokeAll 和 invokeAny 提供更多控制以同时运行任何或所有线程 shutdown 提供了完成所有线程分配工作的能力 Scheduled Executor Services 提供了生成可运行对象和可调用对象的重复调用的方法 希望对你有帮助

【讨论】:

第二点不是“未来”而不是 Callable 吗? Future 是我们可以在线程完成后检索结果/值的地方。 是的,例如。 Future future = executorService.submit(callable);【参考方案5】:

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());
    

【讨论】:

【参考方案6】:

创建一个新线程真的那么贵吗?

作为基准,我刚刚使用 Runnables 和空的 run() 方法创建了 60,000 个线程。创建每个线程后,我立即调用了它的start(..) 方法。这需要大约 30 秒的密集 CPU 活动。针对this question做了类似的实验。总结就是如果线程没有立即结束,大量活动线程累积(几千个),那么就会出现问题:(1)每个线程都有一个堆栈,所以你会耗尽内存, (2) 操作系统对每个进程的线程数可能有限制,但not necessarily, it seems。

所以,据我所知,如果我们谈论的是每秒启动 10 个线程,并且它们都比新线程的启动速度更快,我们可以保证不会超过这个速度,那么 ExecutorService 在可见的性能或稳定性方面没有提供任何具体的优势。 (尽管在代码中表达某些并发想法可能仍然更方便或更易读。)另一方面,如果您可能每秒调度数百或数千个任务,这需要时间来运行,您可能会遇到大问题马上。这可能会意外发生,例如如果您创建线程以响应对服务器的请求,并且您的服务器接收到的请求强度会出现峰值。但是例如一个线程来响应每个用户输入事件(按键、鼠标移动)似乎非常好,只要任务很简短。

【讨论】:

【参考方案7】:

在 java 1.5 版本之前,Thread/Runnable 是为两个独立的服务设计的

    工作单位 该工作单元的执行

ExecutorService 通过将 Runnable/Callable 指定为工作单元并将 Executor 指定为执行(通过生命周期)工作单元的机制来解耦这两个服务

【讨论】:

【参考方案8】:

执行器框架

//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 来处理异步任务

【讨论】:

【参考方案9】:

在没有最大阈值限制的情况下创建大量线程可能会导致应用程序耗尽堆内存。因此,创建 ThreadPool 是更好的解决方案。使用 ThreadPool 我们可以限制可以池化和重用的线程数。

Executors 框架有助于在 java 中创建线程池。 Executors 类使用 ThreadPoolExecutor 提供了 ExecutorService 的简单实现。

来源:

What is Executors Framework

【讨论】:

以上是关于使用 ExecutorService 有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

Executor 代替 ExecutorService 有啥场景吗? Executor 接口背后的意图?

Java 中这段代码中的 ExecutorService.submit 和 ExecutorService.execute 有啥区别?

Java ExecutorService四种线程池使用详解

全局资源 URI 有啥好处(即可寻址性)?

使用 NSOutputstream 有啥好处?

使用静态方法有啥好处?