56

使用将 a 传递给构造函数ExecutorService的运行线程有什么好处?RunnableThread

4

9 回答 9

51

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

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

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

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

此外,尽管样板的外观和感觉,总结并发实用程序的 Oracle API 页面包括一些非常可靠的使用它们的论据,尤其是:

开发人员很可能已经了解标准库类,因此无需学习自组织并发组件的 API 和行为。此外,当并发应用程序构建在可靠且经过良好测试的组件上时,它们的调试要简单得多。

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

于 2010-10-21T03:38:24.380 回答
20

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

ExecutorService exec = Executors.newFixedThreadPool(2);

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

exec.shutdown();

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

对于运行单个线程,我看不到使用 ExecutorService 的任何明显优势。

于 2010-10-21T03:39:56.790 回答
14

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

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

线程池的好处

  • 线程池的使用通过避免在请求或任务处理期间创建线程来减少响应时间。

  • 使用线程池允许您根据需要更改执行策略。您可以通过替换 ExecutorService 实现从单线程到多线程。

  • Java 应用程序中的线程池通过创建基于系统负载和可用资源决定的配置数量的线程来增加系统的稳定性。

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

来源

于 2015-12-19T11:21:51.673 回答
8

以下是一些好处:

  1. Executor 服务以异步方式管理线程
  2. 使用 Future callable 获取线程完成后的返回结果。
  3. 管理工作分配以释放线程并从线程转售已完成的工作以自动分配新工作
  4. fork - 用于并行处理的连接框架
  5. 线程之间更好的通信
  6. invokeAll 和 invokeAny 提供更多控制以一次运行任何或所有线程
  7. 关闭提供完成所有线程分配工作的能力
  8. Scheduled Executor Services 提供了产生runnables和callables的重复调用的方法希望它会帮助你
于 2013-07-01T08:01:06.737 回答
4

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

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

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

于 2016-02-27T09:16:12.503 回答
3

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());
    }
于 2015-06-11T12:19:16.150 回答
2

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

  1. 工作单位
  2. 执行该工作单元

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

于 2015-02-13T04:59:22.423 回答
1

执行器框架

//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只是一个接受的接口Runnableexecute()方法可以只调用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实现的工厂类[关于]async

class Executors 
    newFixedThreadPool() returns ThreadPoolExecutor
    newCachedThreadPool() returns ThreadPoolExecutor
    newSingleThreadExecutor() returns FinalizableDelegatedExecutorService
    newWorkStealingPool() returns ForkJoinPool
    newSingleThreadScheduledExecutor() returns DelegatedScheduledExecutorService
    newScheduledThreadPool() returns ScheduledThreadPoolExecutor
    ...

结论

使用Thread对 CPU 和内存来说是一项昂贵的操作。 ThreadPoolExecutor由 Task Queue( BlockingQueue) 和 Thread Pool(Set of Worker) 组成,它们具有更好的性能和 API 来处理异步任务

于 2021-03-10T15:12:01.273 回答
0

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

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

来源:

什么是执行者框架

于 2018-06-18T16:07:28.927 回答