使用ExecutorService有什么好处?

时间:2010-10-21 03:31:06

标签: java multithreading concurrency executorservice

使用ExecutorService而不是运行将Runnable传递给Thread构造函数的线程有什么好处?

9 个答案:

答案 0 :(得分:40)

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。如果你还没有,请给自己一份。这种全面的并发方法远远超出了这个问题,从长远来看,这将为您节省很多心痛。

答案 1 :(得分:18)

我看到的一个优点是管理/调度多个线程。使用ExecutorService,您不必编写自己的线程管理器,这可能会受到bug的困扰。如果您的程序需要一次运行多个线程,这将特别有用。例如,您希望一次执行两个线程,您可以轻松地执行此操作:

ExecutorService exec = Executors.newFixedThreadPool(2);

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

exec.shutdown();

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

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

答案 2 :(得分:10)

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

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

线程池的好处

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

  • 使用线程池可以根据需要更改执行策略。只需替换ExecutorService实现,就可以从单线程转到多线程。

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

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

Source

答案 3 :(得分:7)

以下是一些好处:

  1. Executor服务以异步方式管理线程
  2. 使用callable在线程完成后获取返回结果。
  3. 管理工作分配以释放线程并从线程转售已完成的工作以自动分配新工作
  4. fork - 用于并行处理的框架
  5. 线程之间更好的沟通
  6. invokeAll和invokeAny提供更多控制权来一次运行任何或所有线程
  7. shutdown提供完成所有线程分配工作的功能
  8. Scheduled Executor Services提供了生成runnables和callables的重复调用的方法 希望它能帮到你

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

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 :(得分:0)

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

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

答案 7 :(得分:0)

创建大量线程而不限制最大阈值可能导致应用程序耗尽堆内存。因为创建一个ThreadPool是更好的解决方案。使用ThreadPool,我们可以限制可以合并和重用的线程数。

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

<强>来源:

What is Executors Framework

答案 8 :(得分:0)

执行器框架

//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] - {{1} }, get()

cancel()

interface ExecutorService extends Executor Future<?> submit(Runnable task) shutdown() ... 扩展 ScheduledExecutorService 用于计划执行任务

ExecutorService

interface ScheduledExecutorService extends ExecutorService schedule() 类是一个工厂,用于为运行 Executors 个任务[About]

提供 ExecutorService 实现
async

结论

使用 class Executors newFixedThreadPool() returns ThreadPoolExecutor newCachedThreadPool() returns ThreadPoolExecutor newSingleThreadExecutor() returns FinalizableDelegatedExecutorService newWorkStealingPool() returns ForkJoinPool newSingleThreadScheduledExecutor() returns DelegatedScheduledExecutorService newScheduledThreadPool() returns ScheduledThreadPoolExecutor ... 对 CPU 和内存来说是一项代价高昂的操作。 Thread 由 Task Queue(ThreadPoolExecutor) 和 Thread Pool(Set of BlockingQueue) 组成,它们具有更好的性能和 API 来处理异步任务