RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时、高可靠的消息发布与订阅服务。
这篇文章,笔者整理了 RocketMQ 源码中创建线程的几点技巧,希望大家读完之后,能够有所收获。
首先我们先温习下常用的创建单线程的两种方式:
▍一、实现 Runnable 接口
图中,MyRunnable 类实现了 Runnable 接口的 run 方法,run 方法中定义具体的任务代码或处理逻辑,而Runnable 对象是作为线程构造函数的参数。
▍二、 继承 Thread 类
线程实现类直接继承 Thread ,本质上也是实现 Runnable 接口的 run 方法。
创建单线程的两种方式都很简单,但每次创建线程代码显得有点冗余,于是 RocketMQ 里实现了一个抽象类 ServiceThread 。
抽象类 ServiceThread
我们可以看到抽象类中包含了如下核心方法:
下图展示了 RocketMQ 众多的单线程实现类。
实现类的编程模版类似 :
我们仅仅需要继承抽象类,并实现 getServiceName 和 run 方法即可。启动的时候,调用 start 方法 , 关闭的时候调用 shutdown 方法。
线程池是一种基于池化思想管理线程的工具,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
JDK中提供的 ThreadPoolExecutor 类,是我们最常使用的线程池类。
ThreadPoolExecutor构造函数
参数名 | 作用 |
---|---|
corePoolSize | 队列没满时,线程最大并发数 |
maximumPoolSizes | 队列满后线程能够达到的最大并发数 |
keepAliveTime | 空闲线程过多久被回收的时间限制 |
unit | keepAliveTime 的时间单位 |
workQueue | 阻塞的队列类型 |
threadPoolFactory | 改变线程的名称、线程组、优先级、守护进程状态 |
RejectedExecutionHandler | 超出 maximumPoolSizes + workQueue 时,任务会交给RejectedExecutionHandler来处理 |
任务的调度通过执行 execute方法完成,方法的核心流程如下:
在 RocketMQ 里 ,网络请求都会携带命令编码,每种命令映射对应的处理器,而处理器又会注册对应的线程池。
当服务端 Broker 接收到发送消息命令时,都会有单独的线程池 sendMessageExecutor 来处理这种命令请求。
基于 ThreadPoolExecutor 做了一个简单的封装 ,BrokerFixedThreadPoolExecutor 构造函数包含六个核心参数:
RocketMQ 实现了一个简单的线程工厂:ThreadFactoryImpl,线程工厂可以定义线程名称,以及是否是守护线程 。
线程工厂
开源项目 Cobar ,Xmemcached,Metamorphosis 中都有类似线程工厂的实现 。
线程名很重要,线程名很重要,线程名很重要 ,重要的事情说三遍。
我们看到 RocketMQ 中,无论是单线程抽象类还是多线程的封装都会配置线程名 ,因为通过线程名,非常容易定位问题,从而大大提升解决问题的效率。
定位的媒介常见有两种:日志文件和堆栈记录。
▍一、日志文件
经常处理业务问题的同学,一定都经常与日志打交道。
▍二、堆栈记录
jstack 是 java 虚拟机自带的一种堆栈跟踪工具 ,主要用来查看 Java 线程的调用堆栈,线程快照包含当前 java 虚拟机内每一条线程正在执行的方法堆栈的集合,可以用来分析线程问题。
jstack -l 进程pid
笔者查看线程堆栈,一般关注如下几点:
本文是RocketMQ 系列文章的开篇,和朋友们简单聊聊 RocketMQ 源码里创建线程的技巧。
RocketMQ 的多线程编程技巧很多,比如线程通讯,并发控制,线程模型等等,后续的文章会一一为大家展现。