[Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors ...

摘要

Java 和其他平台相比最大的优势在于它可以很好的利用资源来进行并行计算。确实,在 JVM 上可以轻而易举地在后台执行一段代码,并在需要使用它的时候消费计算的结果。同时,它也让开发者可以更好的利用现代计算机硬件所带来计算能力。

但是,想让计算正确并不容易,或许对于开发者最大的挑战是编写一个总是能运行正确的程序,而不是我们熟悉的 “在我机器上” 是正确的。

这篇文章会看看 Executor 里提供的不同选择。

正文

Java Executors 详解

简言之,Executor 是一个接口,它旨在将任务的声明与实际计算解耦。

public interface Executor {void execute(Runnable command);}

它以 Runnable 实例的形式接受任务。线程会在某个时间点获取任务并执行 Runnable::run 方法。但是,真正有难度的通常是如何选择将要使用的 Executor 实现。在 Executors 类中已经有一些可供使用的默认实现。让我们来看看它们是什么以及何时选择。

总体上说,当选择供后台计算的 Executor 时,通常可以考虑 3 个主要的问题:

  • 默认希望有多少个线程并行执行?
  • 当所有可用线程都处于忙碌状态时,Executor 会如何处理一个提交的任务?(如:通常它要么使用更多的线程或者要么将任务加入到队列中)
  • 是否希望限制任务队列的大小,如果队列满了会怎样?

如果希望显式地控制这些问题的答案,可以使用 JDK 提供的灵活的 API 来创建自己的 ThreadPoolExecutor 。ThreadPoolExecutor 的构造器显式地要求提供问题的答案:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

以下描述了这些参数的含义以及它们是如何回答以上问题的:

  • int corePoolSize - 线程池初始启动时线程的数量
  • int maximumPoolSize - 当核心线程繁忙时,希望线程池增长的最大值
  • long keepAliveTime, TimeUnit 单位 - 如果线程空闲不再工作,是否希望关闭它?线程池需要等待多久才关闭它?
  • BlockingQueue workQueue - 任务如果不能立即处理是如何处理的?是否希望限制任务队列的大小?
  • RejectedExecutionHandler handler - 如果 Executor 不接受任务会怎样?是否应该抛出异常?调用方是否应该自行处理任务?

下面列出了由工厂方法 Executors 创建的不同 ExecutorServices 的差异。希望对在面临如上问题并做出选择时有帮助。

newFixedThreadPool(int nThread) - n 个线程会同时进行处理,当线程池满后,新的任务会被加入到大小没有限制的队列中。比较适合 CPU 密集型的任务。

newWorkStealingPool(int parallelism) - 会更加所需的并行层次来动态创建和关闭线程。它同样会试图减少任务队列的大小,所以比较适于高负载的环境。同样也比较适用于当执行的任务会创建更多任务,如递归任务。

newSingleThreadExecutor() - 创建一个不可配置的 newFixedThreadPool(1),所以一个线程会执行所有的任务。它适用于明确知道可预测性以及任务需要按顺序执行的情况。

newCachedThreadPool() - 不会将任务加入队列。可以将它看成一个最大值为 0 的队列。如果当前线程都处于繁忙状态,它会创建另外一个线程来执行任务。它有时也会重用线程。它适用于防止 DOS 攻击。缓存线程池的问题在于它不知道该合适停止创建线程。设想需要执行大量计算的任务时,如果将任务提交给 Executor ,更多的线程会消耗更多的 CPU,同时每个任务的执行也会花更长时间。这是个多米诺效应,有更多的任务会被记录下。这样越来越多的线程会被创建,而任务的执行会更慢。很难解决这个负反馈环的问题。

所以对于大多数情况, Executors::newFixedThreadPool(int nThreads) 是当我们想要使用线程池时首先考虑的选择对象。对于计算密集型的任务它通常能提供近于最优的吞吐量,对于 IO 密集型的任务也不会使任何问题变得更糟。至少如果在我们使用这些 Executor 遇到问题并进行性能调优时,不会毫无头绪。

ForkJoinPool 与 ManagedBlockers

当然 JVM 上有一个默认选择的 Executor:通用的 ForkJoinPool,它是由 JVM 预设的用来并行处理流以及执行类似 CompletableFuture::supplyAsync 的任务。

听起来很美?预设的,随时随地可用,最先进的线程池。还希望哪些其他的特性?这里有个忠告,如果有件事情听起来太好了,那么一定需要擦亮眼睛。ForkJoinPool 简直太好了,除了它是通用的 common ,(即被整个 JVM 共享),它可以被在同一 JVM 进程内的所有、任何组件使用。

如果不小心让不合适的任务污染了它,可能会让整个 JVM 进程受到影响。所以如果不小心让 common 池中的工作线程阻塞,可能是没有正确地使用它。

让我们来看看如何让它变得更好。ForkJoinPool 设计的初衷是为了解决有些任务会阻塞工作线程的情况,所以它提供了处理这种阻塞的 API 。

让我们欢迎 — ManagedBlocker - 可以用它来给 ForkJoinPool 传递信号扩展它的并行能力,从而补偿潜在可能被阻塞的工作线程。

假设我们有一个 Call 实例,与 Retrofit 2 Call 类似,它包含所有查询所需要的 endpoint 信息,以及如何将结果转换成对象的信息。开始使用 Retrofit 2,尽管这篇文章主要是写 Android ,但总体的概念与在 JVM 上使用 Retrofit 是一样的。它提供了一套很好的 HTTP 请求的 API 。

class WS<E> implements ManagedBlocker {private final Call<E> call;volatile E item = null;public WS(Call<E> call) {this.call = call;}public boolean block() throws InterruptedException {if (item == null)item = call.execute().body();return true;}public boolean isReleasable() {return item != null;}public E getItem() { // call after pool.managedBlock completesreturn item;}}

现在,当我们想要调用 Call::execute 的时候,我们需要保证它是通过 ForkJoinPool::managedBlock 方法进行调用的

    WS ws = new WS(call);ForkJoinPool.managedBlock(ws);ws.getItem(); // obtain the result

显然,当在 FJP 以外运行的时候它毫无意义,在线程池上运行时才有意义。FJP 会在线程出现阻塞时生成多的工作线程。需要提醒的是,这并不是银弹,相反,很有可能是错误的,因为 ManagedBlocker API 是用来处理可能被阻塞的 synchronizer 对象的。这里我们是在处理一个阻塞网络调用,它可以处理当我们查询 4 个 urls FJP 计算资源被耗尽的情况。

总结

本篇文章我看了 Executors 类提供给我们的选择,以及何时使用各个 Executor 的策略。对于 CPU 密集型的任务,newFixedThreadPool 可以适用大多数场景,除非明确知道另外一个选择更好。但是,对于 IO 密集型的任务,并不简单。可以通过将 IO 调用包装到 ManagedBlocker 里并使用 ForkJoinPool 来增强它内部的并行能力。

参考

zeroturnaround: FixedThreadPool, CachedThreadPool, or ForkJoinPool? Picking correct Java executors for background tasks

结束

转载于:https://www.cnblogs.com/richaaaard/p/6601445.html

[Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors...相关推荐

  1. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  2. 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

    文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...

  3. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  4. Java并发编程:线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  5. (转)Java并发编程:线程池的使用

    背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...

  6. Java并发编程一线程池简介

    推荐:Java并发编程汇总 Java并发编程一线程池简介 为什么我们需要使用线程池? 我们知道线程是一种比较昂贵的资源,我们通过程序每创建一个线程去执行,其实操作系统都会对应地创建一个线程去执行我们的 ...

  7. Java并发编程一线程池的五种状态

    推荐:Java并发编程汇总 Java并发编程一线程池的五种状态 原文地址 Java多线程线程池(4)–线程池的五种状态 正文 线程池的5种状态:Running.ShutDown.Stop.Tidyin ...

  8. Java并发编程之线程池及示例

    1.Executor 线程池顶级接口.定义方法,void execute(Runnable).方法是用于处理任务的一个服务方法.调用者提供Runnable 接口的实现,线程池通过线程执行这个 Runn ...

  9. Java并发编程之线程池ThreadPoolExecutor解析

    线程池存在的意义 平常使用线程即new Thread()然后调用start()方法去启动这个线程,但是在频繁的业务情况下如果在生产环境大量的创建Thread对象是则会浪费资源,不仅增加GC回收压力,并 ...

  10. 《Java 并发编程》线程池

    <Java 并发编程>专栏索引

最新文章

  1. Cordova入门系列(一)创建项目
  2. 重磅!Github 开放无数量限制的免费私有仓库!
  3. MySQL(八)子查询和分组查询
  4. RecycleView 与 Elevation
  5. C语言 main 函数 - C语言零基础入门教程
  6. python查看内置模块,python快速查看内置模块函数
  7. android listview下拉刷新动画,android 安卓 listview 支持下拉刷新 上拉加载更多
  8. Linux常用命令集
  9. ajax传输 基础一
  10. 虚拟打印机 服务器,PdfFactory(虚拟打印机)
  11. 阿里 vs. 腾讯,谁的收购更有眼光?
  12. Pygame实战:牛,几千行代码实现《机甲闯关冒险游戏》,太牛了(保存起来慢慢学)
  13. Topic 18. 临床预测模型之缺失值插补方法
  14. educoder1-2Python 计算思维训练——公式计算
  15. Discussing a meal讨论餐饭(口语小白)
  16. 可道云 docker 群晖_群晖NAS安装影视客户端新手教程,手把手教你NAS怎么下载电影...
  17. 【Ice】【01】linux 安装ice
  18. 22考研基础阶段计划;超七成考研人已开始复习!
  19. 大数据杀熟对哪些人有害?
  20. 交换机连接控制器_干货丨FIT控制器与eMotion LV1的配置场景介绍

热门文章

  1. PM2中无法开启ES6的解决方案
  2. 转[WinForm] VS2010发布、打包安装程序(超全超详细)
  3. 快速批量导入庞大数据到SQL SERVER数据库(ADO.NET)
  4. jQuery Easing 动画效果扩展--使用Easing插件,让你的动画更具美感。
  5. 剑指offer(Java实现) 求1+2+3+…+n
  6. Pycharm的.py文件的导入
  7. Java运行时动态加载类之ClassLoader加载class及其依赖jar包
  8. JSON||获取数据||json数据语法
  9. Java 技术篇-利用exe4j工具生成exe文件实例演示,IntelliJ IDEA将项目转化为jar包方法,运行生成后的程序弹出exe4j提示处理,生成的程序显示控制台设置方法
  10. 简单的按键控制LED