Java并发框架:Executor API详解

介绍

随着当今处理器中可用的核心数量的增加, 随着对实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行。 Java 提供了自己的多线程框架,称为 Executor 框架.

1. Executor 框架是什么?

Executor 框架包含一组用于有效管理工作线程的组件。Executor API 通过 Executors 将任务的执行与要执行的实际任务解耦。 这是 生产者-消费者 模式的一种实现。

java.util.concurrent.Executors 提供了用于创建工作线程的线程池的工厂方法。

为了使用 Executor 框架,我们需要创建一个线程池并提交任务给它以供执行。Executor 框架的工作是调度和执行已提交的任务并从线程池中拿到返回的结果。

浮现于脑海中的一个基本的问题是,当我们创建 java.lang.Thread 的对象或调用实现了 Runnable/Callable 接口来达到的程序的并行性时,为什么需要线程池?

答案来源于两个基本面:

  • 为新任务创建新的线程会存在额外的线程创建以及销毁的开销。管理这些线程的生命周期会明显增加 CPU 的执行时间。
  • 不进行任何限制地为每个进程创建线程会导致创建大量线程。这些线程会占用大量内存并引起资源的浪费。当一个线程利用完 CPU 的时间片后另一个线程即将利用CPU的时间片时,CPU 会花费大量的时间来切换线程的上下文。

所有的这些因素都会导致系统的吞吐量下降。线程池通过保持线程一直存活并重用这些线程来克服这个问题。当提交到线程池中的任务多于正在执行的线程时,那些多余的任务将被放到队列中。 一旦执行任务的线程有空闲的了,它们会从队列中取下一个任务来执行。对于 JDK 提供的现成的 executors 此任务队列基本是无界的。

2. Executors 的类型

现在我们已经了解了 executors 是什么, 让我们来看看不同类型的 executors。

2.1 SingleThreadExecutor

此线程池 executor 只有一个线程。它用于以顺序方式的形式执行任务。如果此线程在执行任务时因异常而挂掉,则会创建一个新线程来替换此线程,后续任务将在新线程中执行。

ExecutorService executorService = Executors.newSingleThreadExecutor()

2.2 FixedThreadPool(n)

顾名思义,它是一个拥有固定数量线程的线程池。提交给 executor 的任务由固定的 n 个线程执行,如果有更多的任务,它们存储在 LinkedBlockingQueue 里。这个数字 n 通常跟底层处理器支持的线程总数有关。

ExecutorService executorService = Executors.newFixedThreadPool(4);

2.3 CachedThreadPool

该线程池主要用于执行大量短期并行任务的场景。与固定线程池不同,此线程池的线程数不受限制。如果所有的线程都在忙于执行任务并且又有新的任务到来了,这个线程池将创建一个新的线程并将其提交到 executor。只要其中一个线程变为空闲,它就会执行新的任务。 如果一个线程有 60 秒的时间都是空闲的,它们将被结束生命周期并从缓存中删除。

但是,如果管理得不合理,或者任务不是很短的,则线程池将包含大量的活动线程。这可能导致资源紊乱并因此导致性能下降。

ExecutorService executorService = Executors.newCachedThreadPool();

2.4 ScheduledExecutor

当我们有一个需要定期运行的任务或者我们希望延迟某个任务时,就会使用此类型的 executor。

ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);

可以使用 scheduleAtFixedRate 或 scheduleWithFixedDelay 在 ScheduledExecutor 中定期的执行任务。

scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)

这两种方法的主要区别在于它们对连续执行定期任务之间的延迟的应答。

  • scheduleAtFixedRate:无论前一个任务何时结束,都以固定间隔执行任务。
  • scheduleWithFixedDelay:只有在当前任务完成后才会启动延迟倒计时。

3. 对于 Future 对象的理解

可以使用 executor 返回的 java.util.concurrent.Future 对象访问提交给 executor 的任务的结果。 Future 可以被认为是 executor 对调用者的响应。

Future<String> result = executorService.submit(callableTask);

如上所述,提交给 executor 的任务是异步的,即程序不会等待当前任务执行完成,而是直接进入下一步。相反,每当任务执行完成时,executor 在此 Future对象中设置它。

调用者可以继续执行主程序,当需要提交任务的结果时,他可以在这个 Future对象上调用.get() 方法来获取。如果任务完成,结果将立即返回给调用者,否则调用者将被阻塞,直到 executor 完成此操作的执行并计算出结果。

如果调用者不能无限期地等待任务执行的结果,那么这个等待时间也可以设置为定时地。可以通过 Future.get(long timeout,TimeUnit unit) 方法实现,如果在规定的时间范围内没有返回结果,则抛出 TimeoutException。调用者可以处理此异常并继续执行该程序。

如果在执行任务时出现异常,则对 get 方法的调用将抛出一个ExecutionException。

对于 Future.get()方法返回的结果,一个重要的事情是,只有提交的任务实现了java.util.concurrent.Callable接口时才返回 Future。如果任务实现了Runnable接口,那么一旦任务完成,对 .get() 方法的调用将返回 null。

另一个关注点是 Future.cancel(boolean mayInterruptIfRunning) 方法。此方法用于取消已提交任务的执行。如果任务已在执行,则 executor 将尝试在mayInterruptIfRunning 标志为 true 时中断任务执行。

4. Example: 创建和执行一个简单的 Executor

我们现在将创建一个任务并尝试在 fixed pool executor 中执行它:

public class Task implements Callable<String> {

  private String message;

  public Task(String message) {
    this.message = message;
  }

  @Override
  public String call() throws Exception {
    return "Hello " + message + "!";
  }
}

Task 类实现 Callable 接口并有一个 String 类型作为返回值的方法。 这个方法也可以抛出 Exception。这种向 executor 抛出异常的能力以及 executor 将此异常返回给调用者的能力非常重要,因为它有助于调用者知道任务执行的状态。

现在让我们来执行一下这个任务:

public class ExecutorExample {
  public static void main(String[] args) {

    Task task = new Task("World");

    ExecutorService executorService = Executors.newFixedThreadPool(4);
    Future<String> result = executorService.submit(task);

    try {
      System.out.println(result.get());
    } catch (InterruptedException | ExecutionException e) {
      System.out.println("Error occured while executing the submitted task");
      e.printStackTrace();
    }

    executorService.shutdown();
  }
}

我们创建了一个具有4个线程数的 FixedThreadPool executors,因为这个 demo是在四核处理器上开发的。如果正在执行的任务执行大量 I/O 操作或花费较长时间等待外部资源,则线程数可能超过处理器的核心数。

我们实例化了 Task 类,并将它提交给 executors 执行。 结果由 Future 对象返回,然后我们在屏幕上打印。

让我们运行 ExecutorExample 并查看其输出:

Hello World!

正如所料,任务追加了问候语 Hello 并通过 Future object 返回结果。

最后,我们调用 executorService 对象上的 shutdown 来终止所有线程并将资源返回给 OS。

.shutdown() 方法等待 executor 完成当前提交的任务。 但是,如果要求是立即关闭 executor 而不等待,那么我们可以使用 .shutdownNow() 方法。

任何待执行的任务都将结果返回到 java.util.List 对象中。

我们也可以通过实现 Runnable 接口来创建同样的任务:

public class Task implements Runnable{

  private String message;

  public Task(String message) {
    this.message = message;
  }

  public void run() {
    System.out.println("Hello " + message + "!");
  }
}

当我们实现 Runnable 时,这里有一些重要的变化。

无法从 run() 方法得到任务执行的结果。 因此,我们直接在这里打印。

run() 方法不可抛出任何已受检的异常。

5. 总结

随着处理器时钟速度难以提高,多线程正变得越来越主流。 但是,由于涉及复杂性,处理每个线程的生命周期非常困难。

在本文中,我们展示了一个高效而简单的多线程框架,即 Executor Framework,并解释了它的不同组件。 我们还看了一下在 executor 中创建提交和执行任务的不同示例。

与往常一样,此示例的代码可以在GitHub上找到。

英文原文:https://stackabuse.com/concurrency-in-java-the-executor-framework/

(0)

相关推荐

  • Java 线程池ExecutorService详解及实例代码

    Java 线程池ExecutorService 1.线程池 1.1什么情况下使用线程池 单个任务处理的时间比较短. 将需处理的任务的数量大. 1.2使用线程池的好处 减少在创建和销毁线程上所花的时间以及系统资源的开销. 如果不使用线程池,有可能造成系统创建大量线程而导致消耗系统内存以及"过度切换"; 2.ExecutorService和Executors 2.1简介 ExecutorService是一个接口,继承了Executor, public interface ExecutorS

  • Java ExecutorService四种线程池使用详解

    1.引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行.第三:提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控.但是要做到合理的利用线程池,必须对其原理了如指掌. 2.线程池使用 Executors提供的四种线程 1.newCachedThreadPool创建一个可缓存线程池

  • 在spring boot中使用java线程池ExecutorService的讲解

    1. 认识java线程池 1.1 在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.需处理的任务的数量大 1.2 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存 1.3 线程池包括以下四个基本组成部分: 1.线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务: 2.工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以

  • java多线程并发executorservice(任务调度)类

    复制代码 代码如下: package com.yao; import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledFuture;import java.util.concurrent.TimeUnit; /** * 以下是一个带方法的类,它设置了 ScheduledExecutorService ,2

  • java中Executor,ExecutorService,ThreadPoolExecutor详解

    java中Executor,ExecutorService,ThreadPoolExecutor详解 1.Excutor 源码非常简单,只有一个execute(Runnable command)回调接口 public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thre

  • 详解Java利用ExecutorService实现同步执行大量线程

    自从java1.5以后,官网就推出了Executor这样一个类,这个类,可以维护我们的大量线程在操作临界资源时的稳定性. 先上一段代码吧: TestRunnable.java public class TestRunnable implements Runnable { private String name; public TestRunnable(String name) { this.name = name; } @Override public void run() { while (t

  • java ExecutorService使用方法详解

    下面的例子主要讨论两个问题: 问题1.线程池固定大小,假设为5.那么向线程池放入10个线程,运行效果如何?其他线程的状态? 问题2.那么如何从线程池中移除某一个线程,确切说是使某一个线程成为空闲线程? 例子: package com.dada.executorService; import java.util.concurrent.TimeUnit; public class JobThread extends Thread { // 为线程命名 public JobThread(String

  • Java并发框架:Executor API详解

    介绍 随着当今处理器中可用的核心数量的增加, 随着对实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行. Java 提供了自己的多线程框架,称为 Executor 框架. 1. Executor 框架是什么? Executor 框架包含一组用于有效管理工作线程的组件.Executor API 通过 Executors 将任务的执行与要执行的实际任务解耦. 这是 生产者-消费者 模式的一种实现. java.util.concurrent.Executors 提供了用于创建工作线程的线程池

  • Java线程池Executor用法详解

    目录 线程池类图 线程池的好处 new Thread的弊端 线程池核心类-ThreadPoolExecutor 使用Executors创建线程池 Executors.newCachedThreadPool Executors.newSingleThreadExecutor Executors.newFixedThreadPool Executors.newScheduledThreadPool 总结 如何定义线程池参数 线程池类图 我们最常使用的Executors实现创建线程池使用线程主要是用上

  • Java反射框架Reflections示例详解

    MAVEN 坐标 <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency> Reflections 的作用 Reflections通过扫描classpath,索引元数据,并且允许在运行时查询这些元数据. 获取某个类型的所有

  • java并发编程之cas详解

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值.这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术. CAS的使用场景 在程序和算法中一个经常出现的模式就是"check and act"模式.先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作.下面是一个

  • Java并发编程-volatile可见性详解

    前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么用? volatile主要对所修饰的变量提供两个功能 可见性 防止指令重排序 <br>本篇博客主要对volatile可见性进行探讨,以后发表关于指令重排序的博文. 什么是可见性? 把JAVA内存模型(JMM)展示得很详细了,简单概括一下 1.每个Thread有一个属于自己的工作内存(可以理解为每个

  • Java并发编程之ThreadLocal详解

    目录 一.什么是ThreadLocal? 二.ThreadLocal的使用场景 三.如何使用ThreadLocal 四.数据库连接时的使用 五.ThreadLocal工作原理 六.小结 七.注意点 一.什么是ThreadLocal? ThreadLocal叫做线程本地变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的.ThreadLocal为变量在每个线程中都创建了一个副本,则每个线程都可以访问自己内部的副本变量. 二.ThreadLocal的使用场景 1.当对象

  • Java并发编程ThreadLocalRandom类详解

    目录 为什么需要ThreadLocalRandom ThreadRandom原理详解 为什么需要ThreadLocalRandom java.util.Random一直都是使用比较广泛的随机数生成工具类,而且java.lang.Math中的随机数生成也是使用的java.util.Random实例. 我们下面看一下java.util.Random的使用方法: import java.util.Random; public class code_4_threadRandom { public sta

  • Java MyBatis框架环境搭建详解

    目录 一.MyBatis简介 1.MyBatis历史 2.MyBatis特性 3.MyBatis下载 4.和其它持久化层技术对比 JDBC Hibernate 和 JPA MyBatis 二.搭建MyBatis 1.开发环境 2.创建maven工程 3.创建MyBatis的核心配置文件 4.创建mapper接口 5.创建MyBatis的映射文件 6.通过junit测试功能 7.加入log4j日志功能 一.MyBatis简介 1.MyBatis历史 MyBatis最初是Apache的一个开源项目i

  • 基于Java ORM框架的使用详解

    ORM框架不是一个新话题,它已经流传了很多年.它的优点在于提供了概念性的.易于理解的数据模型,将数据库中的表和内存中的对象建立了很好的映射关系.我们在这里主要关注Java中常用的两个ORM框架:Hibernate和iBatis.下面来介绍这两个框架简单的使用方法,如果将来有时间,我会深入的写一些更有意思的相关文章.HibernateHibernate是一个持久化框架和ORM框架,持久化和ORM是两个有区别的概念,持久化注重对象的存储方法是否随着程序的退出而消亡,ORM关注的是如何在数据库表和内存

  • SimpleCommand框架ImageLoader API详解(三)

    ImageLoader API 详细介绍,具体内容如下 在ImageLoader中有以下几个不同的构造器: /** * 注意: 次构造器不支持下载进度提示功能 * @param context * @param withCache 是否支持缓存 * false--不带缓存 * true--支持缓存功能,默认缓存路径在外置存储缓冲目录中的picasso-big-cache文件夹中 */ public ImageLoader(Context context, boolean withCache) {

随机推荐