跟着RocketMQ源码学习:如何优雅地停止线程

368 阅读4分钟

大家好,本博客致力于分享互联网领域的各种技术干货,欢迎关注我们一起交流一起学习哦!

在日常开发中,线程管理尤其是停止线程的操作往往是个难点。今天我们通过分析RocketMQ中的ServiceThread源码,来看看它是如何设计线程的启动与停止机制的,并总结出一些实际开发中的经验与建议。

说明:rocketmq管理线程的类源码路径:org.apache.rocketmq.common.ServiceThread

1. RocketMQ线程管理类ServiceThread源码解析

ServiceThread是RocketMQ中的一个抽象类,用来管理线程的生命周期。这个类包含了线程的启动、停止和等待机制的逻辑,主要依靠标志位控制中断机制来管理线程状态,确保线程能够优雅、安全地停止。

  • 线程启动与停止标志
protected volatile boolean stopped = false;
protected final AtomicBoolean started = new AtomicBoolean(false);

stopped用来标记线程是否应该停止,而started则表示线程是否已经启动。通过这两个变量,RocketMQ可以在不同的时刻控制线程的生命周期。

线程启动方法

public void start() {
    if (!started.compareAndSet(false, true)) {
        return;
    }
    stopped = false;
    this.thread = new Thread(this, getServiceName());
    this.thread.setDaemon(isDaemon);
    this.thread.start();
}

通过AtomicBoolean确保线程只能被启动一次,并通过设置stopped = false来重新激活线程。

线程的停止方法shutdown()

public void shutdown(final boolean interrupt) {
    if (!started.compareAndSet(true, false)) {
        return;
    }
    this.stopped = true;
    if (hasNotified.compareAndSet(false, true)) {
        waitPoint.countDown(); // 唤醒等待中的线程
    }
    if (interrupt) {
        this.thread.interrupt(); // 中断线程
    }
    try {
        this.thread.join(JOIN_TIME); // 等待线程结束
    } catch (InterruptedException e) {
        log.error("Interrupted", e);
    }
}

这里的停止逻辑首先通过将stopped标记为true通知线程应当停止运行,接着通过interrupt()来中断线程,防止线程因为阻塞操作(如wait()sleep())长时间无法退出。最后调用join()等待线程完全退出。

2. RocketMQ的优雅停止方式

RocketMQ使用了以下几种方式来确保线程能够优雅地停止

  • 标志位控制:
    通过stopped标志,线程在循环或关键位置检测是否应该停止运行。这种方式确保线程能够在恰当时机(比如处理完某个任务)停止,而不是随意被打断。

while (!stopped) {
    // 线程正在处理逻辑
}

中断机制:当线程在阻塞状态时,直接通过interrupt()唤醒它。阻塞操作如wait()sleep()等都会抛出InterruptedException,这时线程会检查stopped标志位并安全退出。

if (interrupt) {
    this.thread.interrupt();
}

等待与唤醒机制:
RocketMQ采用了CountDownLatch来管理线程的等待与唤醒,通过waitForRunning()方法让线程在特定时间内进入等待状态,并通过wakeup()来通知线程继续执行。这种设计确保了线程能够在需要的时候被唤醒,而不是一直等待下去。

protected void waitForRunning(long interval) {
    waitPoint.await(interval, TimeUnit.MILLISECONDS);
}

3. 为什么不能使用Thread.stop()

在早期的Java版本中,Thread.stop()是一个常见的方法,用来强制终止线程,但它存在严重的安全问题Thread.stop()可能会导致线程在不一致的状态下被终止,导致数据被破坏,或者资源没能被正确释放。举例来说,如果线程正在修改共享数据,突然调用Thread.stop(),这段数据可能还没有完成修改就被强行终止了,这种情况极易造成数据不完整或错误。

因此,Thread.stop()被标记为不推荐使用(Deprecated),不应该在现代Java应用中使用。

4. 小结与建议

通过RocketMQ的线程管理设计,我们可以得出以下总结与开发中的实用建议:

  • 标志位控制: 使用volatile标志位控制线程的停止,确保线程能够在合适的时刻主动退出。
    建议:在线程的循环体或关键任务前,检查标志位来决定是否继续运行。
  • 中断机制: 如果线程可能会进入阻塞状态(例如wait()sleep()等),使用interrupt()来强制唤醒线程,避免线程长时间挂起。
    建议:在合适时刻使用interrupt(),并在阻塞方法后处理InterruptedException
  • 等待与唤醒机制: 通过CountDownLatch或其他同步工具控制线程的等待和唤醒,使线程能够在外部条件变化时及时响应。
    建议:对于需要定期或按条件运行的线程,可以使用CountDownLatch来实现等待与唤醒机制。

通过这些设计方式,RocketMQ实现了优雅、安全的线程管理逻辑。对于开发者来说,这也是编写高质量、多线程应用时可以参考的一种模式。