自建线程池优雅下线

什么场景下使用线程池

  1. 同步改异步
  2. 提高并发吞吐量
  3. 多步任务串行改并行

什么场景下不要使用线程池

  1. 系统资源已经接近瓶颈(内存、CPU、IO)
  2. 上游已经有多线程(多线程嵌套、在线程池中创建线程池)

常见线程池

  • (1) newFixedThreadPool
    建立一个线程数量固定的线程池,规定的最大线程数量,超过这个数量之后进来的任务,会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则。
  • (2) newCacheThreadExecutor
    在核心线程达到最大值之前,如果继续有任务进来就会创建新的核心线程,并加入核心线程池;达到最大核心线程数后,新任务进来,优先使用空闲线程,如果没有空闲线程,则新建临时线程.
    newCacheThreadExecutor使用的是SynchronousQueue作为等待队列,他不保存任何的任务,新的任务加入进来之后,他会创建临时线程来进行使用。
  • (3) newScheduledThreadPool
    创建一个线程池,该线程池可以计划在给定的延迟,或周期性地执行。
    使用的就是DelayedWorkQueue作为等待队列,中间进行了一定的等待,等待时间过后,继续执行任务。

阿里线程开发规约

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:

  • 1) FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  • 2) CachedThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

多线程优雅下线

为了保障应用重启过程中异步操作的执行,避免强制退出JVM可能产生的各种问题,我们可以采用关闭钩子、自定义信号的方式,主动的通知JVM退出,并在JVM关闭前,执行应用程序的一些扫尾工作,进一步保证应用程序可以安全的退出。

线程在终止的过程中,应该先进行操作来清除当前的任务,保持共享数据的一致性,然后再停止。

使用线程池优雅下线需考量

  • 及时终止新增进件
  • 尽量保证执行中的任务能够执行完成
  • 减少内存中积压的任务,避免任务无法及时完成

方案

  • 增加关闭钩子(shutdown hooks),监听进程中断信号。
  • 在钩子中将线程池关闭,拒绝新增任务,并等待线程结束。
  • 背压:接受任务时控制待处理队列大小,限制内存中积压的任务数量,以保证在停机等待时间范围内能够处理完。
  • 限流:可以通过Semaphore信号量限流。

使用线程池要将调度和执行分开,执行代码尽量要有自身完备性。

调度方案:

  • 推模式:(外部触发):xxljob, crontab,quartz
  • 拉模式:(死循环,timer):数据库扫表, MQ消费

减少内存中积压的任务

  • 推模式调度时
    在调度入口做进程状态检查,堵塞新任务进件。
  • 拉模式调度时考量点
    在进程得到中断信号时,及时终止新增任务

拉模式死循环线程终止方案:

  • 用共享变量(shared variable)的方式来设置标志,通知线程必须终止。这个共享变量的操作必须保证是同步的。在循环起点判断标志状态
  • 处理InterruptedException,在catch中终止循环。

线程池关闭

shutdown()
将线程池状态置为SHUTDOWN,并不会立即停止:

  • 停止接收外部submit的任务
  • 内部正在跑的任务和队列里等待的任务,会执行完
  • 等到第二步完成后,才真正停止

shutdownNow()
将线程池状态置为STOP。企图立即停止,事实上不一定:

  • 跟shutdown()一样,先停止接收外部提交的任务
  • 忽略队列里等待的任务
  • 尝试将正在跑的任务interrupt中断
  • 返回未执行的任务列表

awaitTermination(long timeOut, TimeUnit unit)
当前线程阻塞,直到

  • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完
  • 或者等超时时间到
  • 或者线程被中断,抛出InterruptedException

创建线程池参数

ThreadPoolExecutor构造参数:

  • corePoolSize 要保留在池中的线程数,也就是线程池核心池的大小
  • maximumPoolSize 最大线程数
  • keepAliveTime 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
  • unit keepAliveTime 参数的时间单位
  • workQueue 用来储存等待执行任务的队列。
  • threadFactory 线程工厂
  • handler 默认的拒绝执行处理程序

参考文献

  1. 关闭线程的正确方法:“优雅”的中断
  2. JVM安全退出
  3. threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别
  4. ThreadPoolExecutor(五)——线程池关闭相关操作
  5. 【Java】interrupt、interrupted和isInterrupted的区别
  6. Semaphore:如何快速实现一个限流器?- 并发工具类
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章