论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理

简介: 本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。在总体设计上,该框架借鉴了Cilk工作窃取框架的核心理念。其核心技术主要聚焦于高效的任务队列构建和管理,以及工作线程的管理。经过实际性能测试,我们发现大多数程序的并行加速效果显著,但仍有优化空间,未来可能需要进一步研究改进方案。

前提介绍

Doug Lea在州立大学奥斯威戈分校(Doug Lea)

摘要

本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。

在总体设计上,该框架借鉴了Cilk工作窃取框架的核心理念。其核心技术主要聚焦于高效的任务队列构建和管理,以及工作线程的管理。经过实际性能测试,我们发现大多数程序的并行加速效果显著,但仍有优化空间,未来可能需要进一步研究改进方案。


引言

Fork/Join并行是一种简单而高效的设计技术。它的算法思想是分而治之算法的并行版本,其典型形式包括:首先将问题分解为两个或更多的子问题,然后对每个子问题进行独立求解,最后将各个子问题的解合并以形成最终的解决方案。

Result solve(Problem problem) {
   
   
     if (problem is small) 
         directly solve problem
     else {
   
   
         split problem into independent parts
         fork new subtasks to solve each part
         join all subtasks
         compose result from subresults
     }
}
AI 代码解读
  • fork操作会启动一个新的并行fork/join子任务。
  • join连接操作会导致当前任务不继续执行,直到子任务完成。

fork/join算法与其他一样,fork/join算法几乎总是递归的、反复拆分子任务,直到它们小到可以用简单、简短的顺序方法解决为止。使用简单、简短的顺序方法。

FJTask是支持这种编程风格的JavaTM框架。FJTask 作为java.util.concurrent包的一部分,可从 http://gee.cs.oswego.edu 获取。

设计

任何支持构建并行执行的子任务的框架来运行fork/join程序。支持构建并行执行的子任务、的框架运行。

不过,java.lang.Thread类(以及 POSIX pthreads 通常是 Java 线程的基础)不是支持 fork/join 程序的最优的工具。

性能优秀

fork/join任务的同步和管理要求相对简单和有规律。其产生的计算图允许采用不同于通用线程所需的调度策略。例如,除了等待子任务外,fork/join 任务从不需要阻塞。因此,通用线程的阻塞状态跟踪被视为一种资源浪费。

此外,fork/join 框架还可以利用工作窃取技术,将任务从繁忙的线程转移到空闲线程,进一步优化并行处理。

任务粒度合理

在基本任务粒度合理的情况下,构建和管理线程的成本可能高于任务本身的计算时间。虽然粒度可以在特定平台上运行程序时进行调整,但极粗粒度会限制利用并行性的机会。

简而言之,标准的线程框架过于复杂,无法满足大多数分叉/连接程序的需求。然而,线程作为其他类型并行和并行编程方式的基础,要仅仅为了支持这种编程风格而消除其开销或调整线程本身的调度是不可能的,或者至少是不切实际的。

Cilk框架和基础

虽然这些想法肯定有更长的历史,但第一个为这些问题提供系统解决方案的编程框架是Cilk。Cilk和其他轻量级可执行框架是在操作系统的基本线程或进程机制之上的特殊目的的框架,支持fork/join。

fork/join的可移植性

这种策略同样适用于Java,尽管Java线程又依赖于更低级别的操作系统功能。创建这样一个Java轻量级执行框架的主要优点是允许fork/join程序以更可移植的方式编写,并在各种支持JVM的系统上运行。

FJTask框架

FJTask框架是基于Cilk中使用的设 计的一个变体。其他变体存在于 Hood, Filaments,stackthreads,以及一些相关的轻量级系统中。

class ATask extends FJTask {
   
   
     public void run() {
   
   
         split...
         fork...
         join...
         compose...
 }
}
AI 代码解读

可执行任务。所有这些框架都将任务映射到线程,其方式与操作系统将线程映射到CPU相同,但在执行映射时,fork/join框架利用了fork/join程序的简单性、规律性和约束。虽然所有这些框架都可以适应(在不同程度上)以不同风格编写的并行程序,但它们针对fork/join设计进行了优化。

设计思路

线程映射关系

已经建立了一个工作线程池。每个工作线程都是一个标准的(“重的”)线程(这里是线程子类FJTaskRunner的一个实例),它负责处理队列中保存的任务。通常,系统上的工作线程数量和CPU核心数一样多。在Cilk等本地框架中,这些线程被映射到内核线程或轻量级进程,然后再映射到CPU。

在Java中,必须信任JVM和OS才能将这些线程映射到CPU。然而,对于操作系统来说,这是一个相对简单的任务,因为这些线程是计算密集型的。任何合理的映射策略都会将这些线程映射到不同的CPU核心上。

拆分子任务

在FJTask框架中,所有的fork/join任务都是轻量级可执行类的实例,而不是线程的实例。这些任务子类化FJTask,而不是线程,因为独立的可执行任务需要实现接口Runnable并定义一个run方法。

此外,这些任务都实现了Runnable接口,这使得它们可以作为正在执行的任务或线程的一部分交替运行。由于任务在FJTask方法支持的受限制的规则下操作,因此对FJTask进行子类化更加方便,以便能够直接调用它们。

排队及调度

在特殊目的的排队和调度规则下,任务通过工作线程得以执行和管理。这些机制通过任务类中的方法触发,主要包括fork、join、完成状态指示器isDone,以及一些实用的方法,如coInvoke,即分叉并随后连接两个或多个任务。

设置调度管理

一个简单的控制和管理工具(这里是FJTaskRunnerGroup)在从普通线程(如在Java程序中执行主任务的线程)调用时,设置工作池并启动给定的分叉/连接任务的执行。

标准示例

作为程序员如何看待这个框架的标准示例,这里是一个计算斐波那契函数的类。

 static final int threshold = 13; 
 volatile int number; // arg/result
     Fib(int n) {
   
    number = n; }
     int getAnswer() {
   
   
         if (!isDone()) 
            throw new IllegalStateException();
             return number;
    }
 public void run() {
   
   
     int n = number;
     if (n <= threshold) // granularity ctl
         number = seqFib(n);
     else {
   
   
         Fib f1 = new Fib(n − 1);
         Fib f2 = new Fib(n − 2);
         coInvoke(f1, f2); 
         number = f1.number + f2.number;
     }
 }
 public static void main(String[] args) {
   
   
     try {
   
   
         int groupSize = 2; // for example 
         FJTaskRunnerGroup group = new FJTaskRunnerGroup(groupSize);
         Fib f = new Fib(35); // for example
         group.invoke(f);
         int result = f.getAnswer();
         System.out.println("Answer: " +result);
     }catch (InterruptedException ex) {
   
   } 
     }
     int seqFib(int n) {
   
   
         if (n <= 1) return n;
             else return seqFib(n−1) + seqFib(n−2);
     }
}
AI 代码解读

这个版本的运行速度至少比在一个新的java.lang中运行的同等程序快30倍。它在维护多线程Java程序的内在可移植性的同时也做到了这一点。程序员典型感兴趣的调优参数:

  • 在构建工作线程时,其数量通常应与平台上的可用CPU数量相匹配(或更少,以保留处理用于其他非相关目的),有时甚至可能更多,以吸收非计算任务。

  • 一个粒度参数用于确定何时生成任务的成本超过了潜在的并行性带来的好处。这个参数更多地依赖于算法本身,而不是平台。通常,我们可以设定一个阈值,当在单处理器上运行时能获得良好的结果,但当存在多个CPU时仍能充分利用它们。这种方法的好处在于它与JVM的动态编译机制相契合,能够更优化地处理小方法。此外,数据局部性的优势也使得fork/join算法在某些情况下优于其他类型的算法。

未完待续

本节内容,给大家带来了对应的fork/join框架的前世今生,以及基于框架的fork和join机制的论文介绍,后续接下来会给大家带来对应的【线程盗取篇章】:论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理(线程盗取)

目录
打赏
0
3
3
1
379
分享
相关文章
智慧产科一体化管理平台源码,基于Java,Vue,ElementUI技术开发,二开快捷
智慧产科一体化管理平台覆盖从备孕到产后42天的全流程管理,构建科室协同、医患沟通及智能设备互联平台。通过移动端扫码建卡、自助报道、智能采集数据等手段优化就诊流程,提升孕妇就诊体验,并实现高危孕产妇五色管理和孕妇学校三位一体化管理,全面提升妇幼健康宣教质量。
54 12
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
124 7
|
1月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
166 60
【Java并发】【线程池】带你从0-1入门线程池
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
68 23
|
25天前
|
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
96 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
140 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
62 13
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。