| # 原因完整说明 & 解决办法
## 一、核心根本原因(程序常驻不退出的3点)
### 1. `System.in.read()` 控制台阻塞等待(最直观原因)
```java
System.in.read();
```
这行代码会**阻塞主线程**,等待你在控制台手动输入字符、按下回车,代码才会往下执行 `testConsumerStop()` 和 `shutdown()`。
只要你不在Console窗口敲内容回车,主线程就卡在这一步,程序自然不会结束。
### 2. RocketMQ 客户端自带**非守护常驻后台线程**
哪怕删掉 `System.in.read()`,生产者Producer、消费者Consumer启动后,内部自带大量常驻非守护线程:
- Netty网络IO线程池、TCP心跳线程、定时路由刷新线程、消费拉取线程
- JVM判定:**存在非守护线程运行,进程不会主动退出**
必须手动调用 `consumer.shutdown()` / `producer.shutdown()` 关闭MQ客户端,销毁这些后台线程,进程才能正常结束。
### 3. 当前日志证明收发完全正常
从控制台日志可以确认:
1. Producer启动成功,消息发送成功,MsgId正常生成
2. 测试Consumer正常启动,成功拉取并消费到测试消息
MQ客户端已经完整运行,常驻线程全部启动。
---
## 二、两种解决方式
### 方式1:手动控制台回车放行(当前代码设计本意)
1. 切换到下方Console控制台窗口
2. 随便敲一个字母,按下**回车键**
3. `System.in.read()` 读取到输入,继续执行:关闭测试消费者 → 执行全局`shutdown()`关闭所有Producer/Consumer → 所有MQ后台线程销毁,程序正常结束。
### 方式2:临时改成自动退出(测试用)
如果不想手动输入,测试完直接自动关闭,可以把阻塞代码替换为休眠,等待消费完成后自动执行关闭:
```java
testConsumerStart();
// 等待2秒,保证消息消费完毕
TimeUnit.SECONDS.sleep(2);
testConsumerStop();
RocketUtil.shutdown();
```
## 三、补充知识点
1. **守护线程区别**
MQ的IO/消费线程都是**非守护线程**,JVM必须等它们全部结束才会退出;垃圾回收、定时任务这类守护线程不会阻碍进程退出。
2. 后续正式项目
SpringBoot环境会由容器管理生命周期,项目关闭时自动触发我们写的JVM关闭钩子 `shutdownHook`,优雅关闭MQ客户端;只有本地main测试程序,需要手动控制启停时机。
3. 额外验证
执行完`shutdown()`后观察日志,会打印`Producer正常关闭`、`Consumer关闭`日志,代表常驻线程回收完毕,程序随即终止。 |
|手机版|小黑屋|梦想之都-俊月星空
( 粤ICP备18056059号 )|网站地图
GMT+8, 2026-6-30 01:54 , Processed in 0.089178 second(s), 18 queries .
Powered by Mxzdjyxk! X3.5
© 2001-2026 Discuz! Team.