- 线程数计算方法:
线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)
- CPU密集型任务:线程数为 CPU 核心数的 1~2 倍
- IO密集型任务:线程数一般会大于 CPU 核心数很多倍
- 分治算法:将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解
- 步骤:分解;求解;合并
- 在分治法中,子问题一般是相互独立的,因此,经常通过递归调用算法来求解子问题
- Fork/Join:ForkJoinPool允许其他线程向它提交任务,并根据设定将这些任务拆分为粒度更细的子任务,这些子任务将由ForkJoinPool内部的工作线程来并行执行,并且工作线程之间可以窃取彼此之间的任务
- 最适合计算密集型任务,而且最好是非阻塞任务
- ForkJoinPool 是用于执行 ForkJoinTask 任务的执行池
- 维护了一个队列数组 WorkQueue(WorkQueue[]),这样在提交任务和线程任务的时候大幅度减少碰撞
- 四个核心参数:并行数、工作线程的创建、异常处理和模式指定
- 任务提交
- 提交异步执行:execute
- 等待并获取结果:invoke
- 提交执行获取Future结果:submit
- ForkJoinTask 定义了任务执行时的具体逻辑和拆分逻辑,继承了Future接口
- fork() 提交任务:用于向当前任务所运行的线程池中提交任务。如果当前线程是ForkJoinWorkerThread类型,将会放入该线程的工作队列,否则放入common线程池的工作队列中
- join() 获取任务执行结果:调用join()时,将阻塞当前线程直到对应的子任务完成运行并返回结果
- 通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类
- RecursiveAction 用于递归执行但不需要返回结果的任务
- RecursiveTask 用于递归执行需要返回结果的任务
CountedCompleter<T>
在任务完成执行后会触发执行一个自定义的钩子函数
- ForkJoinPool 的工作原理
- ForkJoinPool 内部有多个工作队列,当我们通过 ForkJoinPool 的 invoke() 或者 submit() 方法提交任务时,ForkJoinPool 根据一定的路由规则把任务提交到一个工作队列中,如果任务在执行过程中会创建出子任务,那么子任务会提交到工作线程对应的工作队列中
- ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务(ForkJoinTask)
- 每个工作线程在运行中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的top,并且工作线程在处理自己的工作队列时,使用的是 LIFO 方式,也就是说每次从top取出任务来执行
- 每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务,窃取的任务位于其他线程的工作队列的base,也就是说工作线程在窃取其他工作线程的任务时,使用的是FIFO 方式
- 在遇到 join() 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成
- 在既没有自己的任务,也没有可以窃取的任务时,进入休眠
- 工作窃取:就是允许空闲线程从繁忙线程的双端队列中窃取任务
- 减少线程竞争任务的可能性:工作线程从它自己的双端队列的头部获取任务。但是,当自己的任务为空时,线程会从其他繁忙线程双端队列的尾部中获取任务
- 工作窃取队列(work-stealing queues ):由内部类WorkQueue实现,它是Deques的特殊形式,但仅支持三种操作方式:push、pop和poll(也称为窃取)
- 队列的读取有着严格的约束,push和pop仅能从其所属线程调用,而poll则可以从其他线程调用
- 为什么工作线程总是从头部获取任务,窃取线程从尾部获取任务
- 通过始终选择最近提交的任务,可以增加资源仍分配在CPU缓存中的机会
- 窃取者之所以从尾部获取任务,则是为了降低线程之间的竞争可能
- 由于任务是可分割的,那队列中较旧的任务最有可能粒度较大,因为它们可能还没有被分割,而空闲的线程则相对更有“精力”来完成这些粒度较大的任务
- 工作队列 WorkQueue
- WorkQueue 是双向列表,用于任务的有序执行,如果 WorkQueue 用于自己的执行线程 Thread,线程默认将会从尾端选取任务用来执行 LIFO
- 每个 ForkJoinWorkThread 都有属于自己的 WorkQueue,但不是每个 WorkQueue 都有对应的 ForkJoinWorkThread
- 没有 ForkJoinWorkThread 的 WorkQueue 保存的是 submission,来自外部提交,在
WorkQueues[]
的下标是偶数位
- ForkJoinWorkThread 是用于执行任务的线程,用于区别使用非 ForkJoinWorkThread 线程提交task。启动一个该 Thread,会自动注册一个 WorkQueue 到 Pool,拥有 Thread 的 WorkQueue 只能出现在
WorkQueues[]
的奇数位
上一篇
Java并发-Future
2024-06-13
下一篇
Java并发-Disruptor
2024-06-13