Java 并发之线程池学习

时间:2022-04-27
本文章向大家介绍Java 并发之线程池学习,主要内容包括创建、提交任务、2. submit(Runnable)、关闭线程、线程池的配置、实例分析、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

创建

通过ThreadPoolExecutor来创建一个线程池

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

参数说明:

  • corePoolSize 线程池中任务的基本个数
    • 新提交一个任务时,若线程池中个数未达到基本个数,则新建一个线程
    • 到线程池中的线程数达到基本个数时,再提交任务,则看是否有空闲线程,有则只直接使用
    • 若无空闲线程,则新几条的任务放入排队
  • maximumPoolSize 线程chi池中任务的做多个数
    • 当线程池中个数达到 corePoolSize & 且队列排满了
    • 新创建线程来执行任务
    • 当线程池中任务达到maximumPoolSize,则不再创建
  • keepAliveTime 线程池的工作线程空闲后存活的时间
  • milliseconds 配合上个参数使用,表示时间的单位,如TimeUnit.SECONDS
  • runnableTaskQueue 排队队列
    • ArrayBlockingQueue基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
    • LinkedBlockingQueue 基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue
    • SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
    • PriorityBlockingQueue 一个具有优先级得无限阻塞队列
  • threadFactory 创建线程的工厂,通常会重新指定线程名,方便debug
  • handler 线程池饱和策略
    • 当线程数达到 maximumPoolsize 队列已满时,表示饱和
    • CallerRunsPolicy 只用调用者所在线程来运行任务
    • DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务
    • DiscardPolicy 不处理,丢弃掉
    • AbortPolicy 抛异常
    • 也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略

线程池提交任务的处理流程

提交任务

1. execute(Runnable)

直接执行一个实现了Runnable的接口,即表示提交了一个异步任务给线程池

2. submit(Runnable)

相比较于上面的,区别是这个会返回一个 Future<V> 对象,通过调用future.get() 可以获取线程的返回值,其中这个方程是线程阻塞的,直到返回了结果之后,才会继续执行下去

关闭线程

线程池的shutdown或shutdownNow方法来关闭线程池

shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

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

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

至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow

线程池的配置

分析

  1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
    • CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池
    • IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu
    • 混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解
  2. 任务的优先级:高,中和低。
    • 优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理
  3. 任务的执行时间:长,中和短。
    • 执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。
    • 因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU

获取线程数

Runtime.getRuntime().availableProcessors()

实例分析

背景:

实现一个异步的报警case,首先是有三种报警方式,邮件、微信、短信;其次是具体的报警都是异步处理(一个报警执行的线程池);要求一分钟内报警有上限设置(即要实现报警的计数与清零);报警的重要性根据邮件-》微信-》短信进行递增,当超过每个报警类型的最低阀值时,晋升报警类型

silver-alarm 一个报警的基本框架

源码传送门