java 使用ExecutorService有哪些优点?

bpsygsoo  于 2022-11-20  发布在  Java
关注(0)|答案(9)|浏览(219)

使用ExecutorService比运行线程将Runnable传递到Thread构造函数有什么优势?

2exbekwf

2exbekwf1#

ExecutorService抽象化了许多与低级抽象(如原始Thread)相关的复杂性,它提供了安全启动、关闭、提交、执行以及在任务成功或突然终止时阻塞的机制(表示为RunnableCallable)。
JCiP,第6.2节,直接从马的嘴:
Executor可能是一个简单的接口,但它构成了支持各种任务执行策略的异步任务执行的灵活而强大的框架的基础。它提供了将task submissiontask execution分离的标准方法,将任务描述为RunnableExecutor实现还提供了生命周期支持和挂钩,用于添加统计信息收集、应用程序管理和监控。... .Using anExecutoris usually the easiest path to implementing a producer-consumer design in your application.
而不是花时间实施(通常是错误的,而且需要付出很大的努力),j.u.concurrent框架允许您将重点放在构建任务、相关性和潜在的并行性上。对于大量的并发应用程序,可以直接识别和利用任务边界并利用j.u.c,从而使您能够专注于真正并发挑战的小得多的子集,这些挑战可能需要更专门的解决方案。
此外,尽管Oracle API page summarizing the concurrency utilities的外观和感觉都是样板文件,但它包含了一些使用它们的真正可靠的理由,尤其是:
开发人员可能已经了解了标准库类,因此无需学习特定并发组件的API和行为。此外,如果并发应用程序是基于可靠的、经过良好测试的组件构建的,则调试起来要简单得多。
Java concurrency in practice是一本关于并发性的好书。如果你还没有读过,那就去买一本吧。这里介绍的并发性的全面方法远远超出了这个问题,从长远来看,它会让你省去很多烦恼。

ruoxqz4g

ruoxqz4g2#

我看到的一个优点是管理/调度多个线程。使用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有什么明显的优势。

jaxagkaj

jaxagkaj3#

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

*糟糕的资源管理即,它不断为每个请求创建新资源。对创建资源没有限制。使用Executor框架,我们可以重用现有资源并对创建资源施加限制。
*不稳健:如果我们继续创建新线程,我们将得到StackOverflowException异常,因此我们的JVM将崩溃。
*间接费用创建时间:对于每个请求,我们需要创建新的资源。创建新的资源是非常耗时的。例如,线程创建〉任务。使用Executor框架,我们可以在线程池中构建。
线程池的优点

  • 使用线程池可以避免在请求或任务处理期间创建线程,从而缩短响应时间。
  • 使用线程池可以根据需要更改执行策略。只需替换ExecutorService实现即可从单线程变为多线程。
  • Java应用程序中的线程池通过创建基于系统负载和可用资源决定的配置数量的线程来增加系统的稳定性。
  • 线程池将应用程序开发人员从线程管理工作中解放出来,使他们能够专注于业务逻辑。

Source

6psbrbz9

6psbrbz94#

以下是一些好处:
1.执行器服务异步管理线程
1.使用Future可调用函数获取线程完成后的返回结果。
1.管理工作分配,以释放线程并从线程转售已完成的工作,以便自动分配新工作
1.并行处理fork-join框架
1.更好的线程间通信

  1. invokeAll和invokeAny给予了更多控制来同时运行任意或所有线程
    1.关闭提供了完成所有线程分配工作的能力
    1.调度执行器服务提供了产生可运行对象和可调用对象的重复调用的方法希望它能对您有所帮助
qjp7pelc

qjp7pelc5#

创建一个新线程真的那么昂贵吗?
作为一个基准测试,我刚刚使用Runnable创建了60,000个线程,这些线程都具有空的run()方法。创建每个线程后,我立即调用其start(..)方法。这花费了大约30秒的CPU密集活动时间。在响应this question时也做了类似的实验。这些实验的总结是,如果线程没有立即完成,并且累积了大量的活动线程(几千个),则会出现问题:(1)每个线程都有一个堆栈,因此您将耗尽内存,(2)操作系统可能会对每个进程的线程数施加限制,但not necessarily, it seems
因此,据我所知,如果我们讨论每秒启动10个线程,并且它们的完成速度都比新线程的启动速度快,我们可以保证不会超过这个速度太多,那么ExecutorService在可见的性能或稳定性方面不会提供任何具体的优势。(尽管用代码表达某些并发思想可能会更方便或更容易阅读。)另一方面,如果您可能每秒要调度数百或数千个任务,这可能会意外地发生,例如,如果您创建线程来响应对服务器的请求,而您的服务器接收到的请求的强度出现峰值。但是,例如,一个线程响应每个用户输入事件(按键,鼠标运动)似乎完全没问题,只要任务简短。

gpfsuwkq

gpfsuwkq6#

ExecutorService还提供对FutureTask的访问,FutureTask将在后台任务完成后将结果返回给调用类。

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

ljsrvy3e7#

在java 1.5版本之前,Thread/Runnable是为两个独立的服务设计的
1.工作单位
1.该工作单元的执行
ExecutorService通过将Runnable/Callable指定为工作单元并将Executor指定为执行(使用生命周期)工作单元的机制,将这两个服务分离

luaexgnf

luaexgnf8#

执行器框架

//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()的方法,该方法返回FutureAbout(https://stackoverflow.com/a/66082407/4770877)-get()cancel()

interface ExecutorService extends Executor 
    Future<?> submit(Runnable task)
    shutdown()
    ...

ScheduledExecutorService扩展了ExecutorService以计划执行任务

interface ScheduledExecutorService extends ExecutorService
    schedule()

Executors类,它是为运行async任务About(https://stackoverflow.com/a/49386140/4770877)提供ExecutorService实现的工厂

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

a7qyws3x

a7qyws3x9#

创建大量线程而不限制最大阈值可能会导致应用程序用尽堆内存。因此,创建ThreadPool是更好的解决方案。使用ThreadPool,我们可以限制可以池化和重用的线程数。
Executors框架简化了在java中创建线程池的过程。Executors类使用ThreadPoolExecutor提供ExecutorService的简单实现。

来源:

What is Executors Framework

相关问题