前言

在上一篇《java线程池,Executors阿里开发规范中为什么禁止使用Executors?》中我们谈及了线程池,同时又发现一个现象,当最大线程数还没有满的时候耗时的任务全部堆积给了单个线程, 代码如下:

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, //corePoolSize100, //maximumPoolSize100, //keepAliveTimeTimeUnit.SECONDS, //unitnew LinkedBlockingDeque<>(100));//workQueuefor (int i = 0; i < 5; i++) {final int taskIndex = i;executor.execute(() -> {System.out.println(taskIndex);try {Thread.sleep(Long.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}});
}
// 输出: 0

下图很形象的说明了这个问题:

那么有没有一种机制,在线程池中还有线程可以提供服务的时候帮忙分担一些已经被分配给某一个线程的耗时任务呢?
答案当然是有的:工作窃取算法

工作窃取 (Work stealing)

这边大家先不要将这个跟java挂钩,因为这个属于算法,一种思想和套路,并不是特定语言特有的东西,所以不同的语言对应的实现也不尽一样,但核心思想一致。 这边会用“工作者”来代替线程的说法,如果在java中这个工作者就是线程。

工作窃取核心思想是,自己的活干完了去看看别人有没有没干完的活,如果有就拿过来帮他干。
大多数实现机制是:为每个工作者程分配一个双端队列(本地队列)用于存放需要执行的任务,当自己的队列没有数据的时候从其它工作者队列中获得一个任务继续执行。

我们来看一张图,这张图是发生了工作窃取时的状态。

可以看到工作者B的本地队列中没有了需要执行的规则,它正尝试从工作者A的任务队列中偷取一个任务。

为什么说尝试?因为涉及到并行编程肯定涉及到并发安全的问题,有可能在偷取过程中工作者A提前抢占了这个任务,那么B的偷取就会失败。大多数实现会尽量避免发生这个问题,所以大多数情况下不会发生。

并发安全的问题是怎么避免的呢?

一般是自己的本地队列采取LIFO(后进先出),偷取时采用FIFO(先进先出),一个从头开始执行,一个从尾部开始执行,由于偷取的动作十分快速,会大量降低这种冲突,也是一种优化方式。

Java中的工作窃取算法线程池

在Java 1.7新增了一个ForkJoinPool类,主要是实现了工作窃取算法的线程池,该类在1.8中被优化了,同时1.8在Executors类中还新增了两个newWorkStealingPool工厂方法。

java7中的fork/join task 和 java8中的并行stream都是基于ForkJoinPool。

// 使用当前处理器数, 相当于调用 newWorkStealingPool(Runtime.getRuntime().availableProcessors());
public static ExecutorService newWorkStealingPool();
public static ExecutorService newWorkStealingPool(int parallelism);

同时 ForkJoinPool 还在全局建立了一个公共的线程池

ForkJoinPool.commonPool();

默认的并行度是当前JVM识别到的处理器数。这个值也是可以通过参数进行变更的,下面是可以通过JVM熟悉进行commonPool设置的参数。

前缀统一为: java.util.concurrent.ForkJoinPool.common. 比如 parallelism 就要写为 java.util.concurrent.ForkJoinPool.common.parallelism

使用工作窃取算法的线程池来优化之前的代码

ExecutorService executor = Executors.newWorkStealingPool(8);for (int i = 0; i < 5; i++) {final int taskIndex = i;executor.execute(() -> {System.out.println(taskIndex);try {Thread.sleep(Long.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}});
}// 无序输出 0~4

如果将Executors.newWorkStealingPool(8)改成ForkJoinPool.commonPool()会输出什么?

如果你能知道输出什么那么你对这个机制就算掌握了,会输出当前运行环境中处理器(cpu)数量的次数(如果核算大于5就只会输出5个结果)。

newWorkStealingPool 和 ForkJoinPool.commonPool 该优先选择哪个?

这个没有最优解,推荐执行的小任务(零散的)使用commonPool,而有特定目的的则使用newWorkStealingPoolnew ForkJoinPool

使用ForkJoinPool.commonPool 需要注意的问题

commonPool默认使用当前环境的处理器格式来当做并行程度,如果遇上堵塞形任务一样会遇到浪费算力的问题。
这点在容器化时需要特别注意,因为容器化的cpu个数限制往往不会太大。
这种时候可以通过设置默认的并行度或者使用newWorkStealingPool来手动指定并行度。

最后

为什么ForkJoinPool极少出现线程关键字?

现在许多语言淡化了线程这个概念,而golang中更是直接去掉了线程能力改为提供协程goroutine
目的还是线程是OS的资源,OS对程序内部运行其实并没有太了解,为了避免线程资源的浪费许多语言会自己管理线程。
对于程序来说我们关心的主要还是任务的并行运行,并不关心是线程还是协程。
下面是一些对应关系: - CPU : 线程 (1:N) - 线程 : 协程 (1:N)

CPU由OS管理,OS提供线程给程序使用,程序利用线程提供协程能力给应用使用。

ForkJoinPool一定更快吗?

不,大家都知道做的事情越多逻辑越复杂效率会越低。
ForkJoinPool中的工作队列,工作窃取都是需要额外管理的,同时也对线程调度和GC带来了压力。 所以ForkJoinPool并不是万能药大家根据具体需要去使用。

后面可能会跟大家分享下 Spring 中的 @Async

中fifo算法_java线程池,工作窃取算法相关推荐

  1. SpringBoot中的异步操作与线程池

    线程池类型 Java通过 java.util.concurrent.Executors 的静态方法提供五种线程池 newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需 ...

  2. 【Android 异步操作】线程池 ( 线程池作用 | 线程池种类 | 线程池工作机制 | 线程池任务调度源码解析 )

    文章目录 一.线程池作用 二.线程池种类 三.线程池工作机制 四.线程池任务调度源码解析 一.线程池作用 线程池作用 : ① 避免创建线程 : 避免每次使用线程时 , 都需要 创建线程对象 ; ② 统 ...

  3. 线程池工作原理和实现原理

    为什么要使用线程池 平时讨论多线程处理,大佬们必定会说使用线程池,那为什么要使用线程池?其实,这个问题可以反过来思考一下,不使用线程池会怎么样?当需要多线程并发执行任务时,只能不断的通过new Thr ...

  4. 25张图展示线程池工作原理和实现原理,建议认真阅读,对你有帮助

    上篇<这样的API网关查询接口优化,我是被迫的>文章末尾,有朋友留言提到文中的场景是IO密集型操作,不是CPU密集操作,不需要使用线程池,我猜这位朋友可能想表达的是IO密集且阻塞时间久的不 ...

  5. java线程池存在时间_Java线程池基础

    目录: 一.线程池概述 1.线程池类 目前线程池类一般有两个,一个来自于Spring,一个来自于JDK: 来自Spring的线程池:org.springframework.scheduling.con ...

  6. 浅谈线程池(中):独立线程池的作用及IO线程池

    在上一篇文章中,我们简单讨论了线程池的作用,以及CLR线程池的一些特性.不过关于线程池的基本概念还没有结束,这次我们再来补充一些必要的信息,有助于我们在程序中选择合适的使用方式. 独立线程池 上次我们 ...

  7. 自定义线程池-线程池工作流程介绍

    ThreadPoolExecutor参数详解 我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数; a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位 ...

  8. 【源码阅读计划】浅析 Java 线程池工作原理及核心源码

    [源码阅读计划]浅析 Java 线程池工作原理及核心源码 为什么要用线程池? 线程池的设计 线程池如何维护自身状态? 线程池如何管理任务? execute函数执行过程(分配) getTask 函数(获 ...

  9. ConcurrentHashMap 锁分段技术 结构 ConcurrentLinkedQueue 阻塞队列 Fork/Join框架 工作窃取算法

    文章目录 ConcurrentHashMap 锁分段技术 结构 ConcurrentLinkedQueue 阻塞队列 Fork/Join框架 工作窃取算法 ConcurrentHashMap 锁分段技 ...

最新文章

  1. CUDA之单thread单block多thread单block多thread多block
  2. 题目1204:农夫、羊、菜和狼的故事
  3. sql注入_1-7_绕过注入
  4. struts2面试问题_Struts2面试问答
  5. python中赋值运算符有哪些_Python代码中有哪些赋值运算符呢?
  6. Java笔记-Semaphore简单应用实例
  7. Multi_thread--Linux下多线程编程互斥锁和条件变量的简单使用
  8. 进程间通信方式_第四十九期-Linux内核中的进程概述(4)
  9. android九宫格忘了,九宫格密码忘了怎么办?九宫格锁屏忘记密码解决方法
  10. MySQL 主主配置
  11. 针对piix4_smbus ****host smbus controller not enabled的解决方法
  12. 计算机专业学生专利,2019.6 电子与计算机工程学院学生喜获三项国家专利
  13. WindowsX64下tftp的安装
  14. 原生ajax上传获取进度,ajax上传图片获取进度
  15. 小熊派华为物联网操作系统LiteOS裸机驱动移植06-E53_ST1扩展板驱动及使用
  16. Oracle 锁详解(lock)
  17. QCC512x QCC302x 打开 BLE 功能
  18. 《ssh权威指南》书评
  19. (五)Vue之data与el的两种写法
  20. 在线流程图和思维导图开发技术详解(四)

热门文章

  1. ORACLE删除表分区和数据
  2. 【Spark】一条 SQL 在 Apache Spark 之旅(上)
  3. 60-320-040-使用-去重-HyperLogLog 去重计数
  4. 【Antlr】unknown attribute text for rule stat in $stat.text
  5. Druid : 慢SQL统计与监控
  6. cdh集群的重启过程
  7. 【SpringBoot】Spring+Druid初级配置
  8. JVM各个组成部分和其基本功能
  9. Java Spring全家桶详解——Spring简介
  10. oracle扩容bigfile,Oracle10g BIGFILE表空间带来的好处