并发编程艺术笔记:Fork/Join、线程池、Executor

 

目录

Fork/Join框架

运行流程

工作窃取算法

核心组件

ThreadPoolExecutor

处理过程

线程池创建

线程池状态

工作线程

线程工厂

线程池调度过程

核心方法execute方法

工作线程的执行

工作线程的清理

任务的获取

线程池拒绝策略

线程池关闭

合理配置线程池

线程池监控

Executor框架

Excutors下的几种线程池

ScheduledThreadPoolExecutor

构造方法

delayedExecute方法

Future核心组件:

FutureTask任务的7种状态

主要属性

任务的运行

任务的取消

结果获取


Fork/Join框架

运行流程

并发编程艺术笔记:Fork/Join、线程池、Executor_第1张图片

工作窃取算法

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行

假如需要做一个比较大的任务,可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。

比如A线程负责处理A队列里的任务。但是,有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行,而这时它们会访问同一个队列。

为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,如LinkedBlockingDeque,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

并发编程艺术笔记:Fork/Join、线程池、Executor_第2张图片

工作窃取算法的优点:充分利用线程进行并行计算,减少了线程间的竞争。

工作窃取算法的缺点:在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且该算法会消耗了更多的系统资源,比如创建多个线程和多个双端队列。

核心组件

  • ForkJoinPool线程池:ExecutorService的实现类,负责工作线程的管理、任务队列的维护,接受fork子任务的提交,接受外部任务的提交,如invoke、execute、submit方法
  • ForkJoinTask任务:Future接口的实现类,fork是其核心方法,用于分解任务并异步执行;而join方法在任务结果计算完毕之后才会运行,用来合并或返回计算结果
  • ForkJoinWorkerThread工作线程:Thread的子类,作为线程池中的工作线程(Worker)执行任务
  • WorkQueue任务队列:在ForkJoinPool中,是一个任务队列,用于保存任务

转载两篇分析源码博文

Java多线程进阶(四三)—— J.U.C之executors框架:Fork/Join框架(1) 原理

Java多线程进阶(四三)—— J.U.C之executors框架:Fork/Join框架(2)实现


ThreadPoolExecutor

在开发过程中,合理地使用线程池能够带来3个好处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

处理过程

并发编程艺术笔记:Fork/Join、线程池、Executor_第3张图片

线程池创建

ThreadPoolExecutor一共提供了4种构造器,但其它三种内部其实都调用了下面的构造器。

并发编程艺术笔记:Fork/Join、线程池、Executor_第4张图片

线程池状态

并发编程艺术笔记:Fork/Join、线程池、Executor_第5张图片

ThreadPoolExecutor一共定义了5种线程池状态:

  • RUNNING : 接受新任务, 且处理已经进入阻塞队列的任务
  • SHUTDOWN : 不接受新任务, 但处理已经进入阻塞队列的任务
  • STOP : 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务
  • TIDYING : 所有任务都已终止, 工作线程数为0, 线程转化为TIDYING状态并准备调用terminated方法
  • TERMINATED : terminated方法已经执行完成

并发编程艺术笔记:Fork/Join、线程池、Executor_第6张图片

工作线程

并发编程艺术笔记:Fork/Join、线程池、Executor_第7张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第8张图片

线程工厂

并发编程艺术笔记:Fork/Join、线程池、Executor_第9张图片

线程池调度过程

核心方法execute方法

并发编程艺术笔记:Fork/Join、线程池、Executor_第10张图片

其中addWorker(null, false),当将任务成功添加到队列后,如果此时的工作线程数为0,就会执行这段代码。

将任务添加到队列后,需要判断工作线程数是否为0,如果是0那么就必须新建一个空任务的工作线程,将来在某一时刻它会去队列取任务执行,否则没有工作线程的话,该队列中的任务永远不会被执行。

addWorker方法

并发编程艺术笔记:Fork/Join、线程池、Executor_第11张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第12张图片

addWorkerFailed方法

并发编程艺术笔记:Fork/Join、线程池、Executor_第13张图片

工作线程的执行

并发编程艺术笔记:Fork/Join、线程池、Executor_第14张图片

其中if判断语句作用是:

  • 保证当线程池状态为STOP、TIDYING、TERMINATED时,当前执行任务的线程wt是中断状态。因为线程池处于上述任一状态时,均不能再执行新任务。
  • 保证当线程池状态为RUNNING、SHUTDOWN时,当前执行任务的线程wt不是中断状态。

并发编程艺术笔记:Fork/Join、线程池、Executor_第15张图片

工作线程需要清理有两种情况:

  • 正常情况下,如果工作线程从队列中获取不到任务,置 completedAbruptly 为false,然后执行清理工作
  • 异常情况下,工作线程在执行过程中被中断或出现其它异常,置 completedAbruptly 为true,然后执行清理工作

工作线程的清理

processWorkerExit的作用就是将该退出的工作线程清理掉,然后判断线程池是否需要终止。

并发编程艺术笔记:Fork/Join、线程池、Executor_第16张图片

任务的获取

getTask方法的主要作用就是:通过自旋,不断地尝试从阻塞队列中获取一个任务,如果获取失败则返回null。

并发编程艺术笔记:Fork/Join、线程池、Executor_第17张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第18张图片

线程池拒绝策略

线程池有两种情况下会执行拒绝策略:

  • 提交任务时,线程池已经关闭了
  • 当核心线程池满了之后,如果工作队列也满了,首先判断非核心线程池有没有满,如果没有满就创建一个工作线程, 否则就会执行拒绝策略

AbortPolicy(默认)

并发编程艺术笔记:Fork/Join、线程池、Executor_第19张图片

DiscardPolicy

并发编程艺术笔记:Fork/Join、线程池、Executor_第20张图片

DiscardOldestPolicy

并发编程艺术笔记:Fork/Join、线程池、Executor_第21张图片

CallerRunsPolicy

并发编程艺术笔记:Fork/Join、线程池、Executor_第22张图片

线程池关闭

原理:遍历线程池中的工作线程,调用interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

并发编程艺术笔记:Fork/Join、线程池、Executor_第23张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第24张图片

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。

合理配置线程池

合理配置线程池,必须首先分析任务特性,可以从以下几个角度来分析:

  • 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
  • 任务的优先级:高、中和低。
  • 任务的执行时间:长、中和短。
  • 任务的依赖性:是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。

由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。

混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。

可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

线程池监控

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,大小只增不减。
  • getActiveCount:获取活动的线程数。

扩展线程池进行监控:可以通过继承线程池来自定义线程池,重写线程池的 beforeExecute、afterExecute和terminated 方法,在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。


Executor框架

Executor框架的两级调度模型:

  • 在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU。
  • 在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

并发编程艺术笔记:Fork/Join、线程池、Executor_第25张图片

即应用程序通过Executor框架控制上层的调度,下层的调度由操作系统内核控制。

Executor框架的使用示意图:

并发编程艺术笔记:Fork/Join、线程池、Executor_第26张图片

Excutors下的几种线程池

固定线程数的线程池:空闲存活时间为0L,故多余的空闲线程会立即被终止。无界队列(队列过长容易导致OOM)。

并发编程艺术笔记:Fork/Join、线程池、Executor_第27张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第28张图片

单个线程的线程池:空闲存活时间为0L,故多余的空闲线程会立即被终止。无界队列(队列过长容易导致OOM)。

并发编程艺术笔记:Fork/Join、线程池、Executor_第29张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第30张图片

可缓存的线程池:如果主线程提交任务的速度高于线程处理任务的速度,CachedThreadPool会不断创建新线程。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源导致OOM。

并发编程艺术笔记:Fork/Join、线程池、Executor_第31张图片

可周期调度的线程池:

并发编程艺术笔记:Fork/Join、线程池、Executor_第32张图片

ScheduledThreadPoolExecutor

构造方法

并发编程艺术笔记:Fork/Join、线程池、Executor_第33张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第34张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第35张图片

delayedExecute方法

并发编程艺术笔记:Fork/Join、线程池、Executor_第36张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第37张图片

Future

核心组件:

  • 真实任务/数据类:通常任务执行比较慢,或数据构造需要较长时间,即任务类,实现Runnable接口
  • Future接口:调用方使用该凭证获取真实任务、数据的结果,即Future接口
  • Future实现类:用于对真实任务、数据进行包装,即FutureTask实现类

FutureTask任务的7种状态

JDK1.7之前,FutureTask通过内部类实现了AQS框架来实现功能。 JDK1.7及以后,则改变为直接通过Unsafe类CAS操作state状态字段来进行同步。

并发编程艺术笔记:Fork/Join、线程池、Executor_第38张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第39张图片

该图转自https://segmentfault.com/a/1190000016767676#articleHeader5

主要属性

并发编程艺术笔记:Fork/Join、线程池、Executor_第40张图片

任务的运行

并发编程艺术笔记:Fork/Join、线程池、Executor_第41张图片

任务的取消

并发编程艺术笔记:Fork/Join、线程池、Executor_第42张图片

finishCompletion方法Removes and signals all waiting threads:

并发编程艺术笔记:Fork/Join、线程池、Executor_第43张图片

结果获取

并发编程艺术笔记:Fork/Join、线程池、Executor_第44张图片

并发编程艺术笔记:Fork/Join、线程池、Executor_第45张图片

参考 《Java并发编程的艺术》

         https://segmentfault.com/blog/ressmix_multithread

你可能感兴趣的:(并发编程)