乐趣区

关于java:读完-RocketMQ-源码我学会了如何优雅的创建线程

RocketMQ 是一款开源的分布式音讯零碎,基于高可用分布式集群技术,提供低延时、高牢靠的音讯公布与订阅服务。

这篇文章,笔者整顿了 RocketMQ 源码中创立线程的几点技巧,心愿大家读完之后,可能有所播种。

1 创立单线程

首先咱们先复习下罕用的创立单线程的两种形式:

  • 实现 Runnable 接口
  • 继承 Thread 类

▍一、实现 Runnable 接口

图中,MyRunnable 类实现了 Runnable 接口的 run 办法,run 办法中定义具体的工作代码或解决逻辑,而 Runnable 对象是作为线程构造函数的参数。

▍二、继承 Thread 类

线程实现类间接继承 Thread,实质上也是实现 Runnable 接口的 run 办法。

2 单线程抽象类

创立单线程的两种形式都很简略,但每次创立线程代码显得有点冗余,于是 RocketMQ 里实现了一个抽象类 ServiceThread。

咱们能够看到抽象类中蕴含了如下外围办法:

  1. 定义线程名;
  2. 启动线程;
  3. 敞开线程。

下图展现了 RocketMQ 泛滥的单线程实现类。

实现类的编程模版相似:

咱们仅仅须要继承抽象类,并实现 getServiceNamerun 办法即可。启动的时候,调用 start 办法,敞开的时候调用 shutdown 办法。

3 线程池原理

线程池是一种基于池化思维治理线程的工具,线程池保护着多个线程,期待着监督管理者调配可并发执行的工作。这防止了在解决短时间工作时创立与销毁线程的代价。线程池不仅可能保障内核的充分利用,还能避免过分调度。

JDK 中提供的 ThreadPoolExecutor 类,是咱们最常应用的线程池类。

参数名 作用
corePoolSize 队列没满时,线程最大并发数
maximumPoolSizes 队列满后线程可能达到的最大并发数
keepAliveTime 闲暇线程过多久被回收的工夫限度
unit keepAliveTime 的工夫单位
workQueue 阻塞的队列类型
threadPoolFactory 扭转线程的名称、线程组、优先级、守护过程状态
RejectedExecutionHandler 超出 maximumPoolSizes + workQueue 时,工作会交给 RejectedExecutionHandler 来解决

工作的调度通过执行 execute 办法实现,办法的外围流程如下:

  1. 如果 workerCount < corePoolSize,创立并启动一个线程来执行新提交的工作。
  2. 如果 workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将工作增加到该阻塞队列中。
  3. 如果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创立并启动一个线程来执行新提交的工作。
  4. 如果 workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则依据回绝策略来解决该工作, 默认的解决形式是间接抛异样。

4 线程池封装

在 RocketMQ 里,网络申请都会携带命令编码,每种命令映射对应的处理器,而处理器又会注册对应的线程池。

当服务端 Broker 接管到发送音讯命令时,都会有独自的线程池 sendMessageExecutor 来解决这种命令申请。

基于 ThreadPoolExecutor 做了一个简略的封装,BrokerFixedThreadPoolExecutor 构造函数蕴含六个外围参数:

  1. 外围线程数和最大线程数雷同,数量是:cpu 核数和 4 比拟后的最小值;
  2. 闲暇线程的回收的工夫限度,默认 1 分钟;
  3. 发送音讯队列,有界队列,默认 10000;
  4. 线程工厂 ThreadFactoryImpl,定义了线程名前缀:SendMessageThread_。

RocketMQ 实现了一个简略的线程工厂:ThreadFactoryImpl,线程工厂能够定义线程名称,以及是否是守护线程。

开源我的项目 Cobar,Xmemcached,Metamorphosis 中都有相似线程工厂的实现。

5 线程名很重要

线程名很重要,线程名很重要,线程名很重要,重要的事件说三遍。

咱们看到 RocketMQ 中,无论是单线程抽象类还是多线程的封装都会配置线程名,因为通过线程名,非常容易定位问题,从而大大晋升解决问题的效率。

定位的媒介常见有两种:日志文件 堆栈记录

▍一、日志文件

常常解决业务问题的同学,肯定都常常与日志打交道。

  • 查看 ERROR 日志,追溯到执行线程,要是线程池隔离做的好,根本能够判断出哪种业务场景出了问题;
  • 通过查看线程打印的日志,推断线程调度是否失常,比方有的定时工作线程打印了开始,没有打印完结,推论以后线程可能曾经挂掉或者阻塞。

▍二、堆栈记录

jstack 是 java 虚拟机自带的一种堆栈跟踪工具,次要用来查看 Java 线程的调用堆栈,线程快照蕴含以后 java 虚拟机内每一条线程正在执行的办法堆栈的汇合,能够用来剖析线程问题。

jstack -l 过程 pid

笔者查看线程堆栈,个别关注如下几点:

  1. 以后 jvm 过程中的线程数量和线程分类是否在预期的范畴内;
  2. 零碎接口超时或者定时工作进行的异样场景下,剖析堆栈中是否有锁未开释,或者线程始终期待网络通讯响应;
  3. 剖析 jvm 过程中哪个线程占用的 CPU 最高。

6 总结

本文是 RocketMQ 系列文章的开篇,和敌人们简略聊聊 RocketMQ 源码里创立线程的技巧。

  1. 单线程抽象类 ServiceThread

    使用者只须要实现业务逻辑以及定义线程名即可,不须要写冗余的代码。

  2. 线程池封装

    适当封装,定义线程工厂,并合理配置线程池参数。

  3. 线程名很重要

    文件日志,堆栈记录配合线程名能大大晋升解决问题的效率。

RocketMQ 的多线程编程技巧很多,比方线程通信,并发管制,线程模型等等,后续的文章会一一为大家展示。

退出移动版