如何优雅的停止线程的故事之“工人与老板的车间”​​

96 阅读5分钟

用一个​​工人与老板的车间故事​​带你彻底搞懂线程中断的底层原理。全程干货,故事+代码+源码解析三连击,看完你就能优雅“指挥”线程下班!


🛑 ​​一、Thread.stop():老板暴力拔电源的灾难现场​

​故事​​:想象工人(线程)正在车间组装汽车(共享资源)。他刚装好引擎(x=3),正准备装轮胎(y=4)时,老板突然冲进来拔掉电源(thread.stop())。结果:引擎装上了,轮胎没装,汽车半成品出厂!其他工人(线程)看到这辆“残废车”直接崩溃。

⚠️ ​​stop()的致命问题(源码级解析)​

  1. ​强制释放所有锁​​:
    stop() 会立即抛出 ThreadDeath 异常(继承自 Error),并​​释放该线程持有的所有锁​​。在源码中(Thread.stop() 的本地方法 stop0()),JVM 会解除线程锁定的所有监视器(monitor)。

    java
    Copy
    synchronized void updateCar() {  
         x = 3;  // 执行完这一步后被 stop(),锁被强制释放!
         y = 4;  // 未执行!其他线程看到 x=3 但 y=0 的破损数据!
    }  
    

    ​结果​​:共享对象(汽车)状态不一致,程序崩溃💥 。

  2. ​资源泄漏​​:
    如果工人正拿着扳手(文件句柄)或占着车位(Socket连接),突然消失会导致资源永远无法释放(扳手丢了,车位废了)。

  3. ​废弃原因​​:
    从 Java 1.2 开始 stop() 被标注 @Deprecated,未来版本可能直接移除。源码中调用它会抛出 UnsupportedOperationException

✅ ​​结论​​:stop() 是线程界的“砍头术”,已彻底废弃!千万别碰!


🚦 ​​二、interrupt():老板的礼貌下班通知​

​故事​​:老板想通知工人下班,不会直接拉闸,而是发微信:“兄弟,该走了!”(thread.interrupt())。工人看到消息,会​​安全收尾​​(关设备、还工具)再离开。

🔧 ​​interrupt() 的底层机制(源码揭秘)​

  1. ​设置中断标志位​​:
    interrupt() 核心操作是修改线程内部的 ​volatile boolean interrupted 标志位​​(源码在 Thread 类中) :

    java
    Copy
    // Thread 类源码(简化)  
    public void interrupt() {  
         synchronized (interruptLock) {  
             interrupted = true;  // 关键:volatile 保证可见性  
         }  
         // 如果线程阻塞在 sleep/wait/join,JVM 会唤醒它并抛 InterruptedException  
         nativeInterrupt();  
    }  
    
  2. ​如何响应中断?工人自主决定​

    • ​情况1:工人正在干活(RUNNING 状态)​
      工人需​​主动检查中断标志​​(isInterrupted()),就像看微信:

      java
      Copy
      while (!Thread.currentThread().isInterrupted()) {  
         // 继续装轮胎...  
      }  
      // 发现中断标志为 true,安全收尾后退出 run()  
      

      ​注意​​:不检查标志的线程会无视中断!

    • ​情况2:工人在睡觉(BLOCKED 状态)​
      如果工人正在午休(sleep(1000)),老板发消息会​​立刻吵醒他​​(抛出 InterruptedException),并​​自动清除中断标志​​(设为 false) :

      java
      Copy
      try {  
         Thread.sleep(1000);  
      } catch (InterruptedException e) {  
         // 被中断唤醒!但此时中断标志已被清除(false)  
         // 正确做法:重置中断标志,让后续代码能检测到  
         Thread.currentThread().interrupt(); // 重新标记“老板催我走”  
         break; // 退出循环  
      }  
      

🛠️ ​​三、正确停止线程的4种姿势(附代码)​

✅ ​​姿势1:循环检查中断标志​​(通用场景)

java
Copy
public class WorkerThread extends Thread {  
    @Override  
    public void run() {  
        while (!isInterrupted()) { // 每次循环检查老板是否喊停  
            System.out.println("装轮胎中...");  
        }  
        System.out.println("收拾工具下班!");  
    }  
}  
// 老板发指令  
workerThread.interrupt();  

​适用​​:线程执行可拆分任务(如循环处理数据) 。


✅ ​​姿势2:阻塞操作捕获 InterruptedException​

java
Copy
public class SleeperThread extends Thread {  
    @Override  
    public void run() {  
        while (!isInterrupted()) {  
            try {  
                Thread.sleep(1000); // 可能被 interrupt() 唤醒  
            } catch (InterruptedException e) {  
                // 重置中断标志并退出  
                Thread.currentThread().interrupt();   
            }  
        }  
    }  
}  

​关键​​:捕获异常后必须​​重置中断标志​​(interrupt()),否则后续逻辑无法感知中断。


✅ ​​姿势3:volatile 标志位 + interrupt() 双保险​

针对​​不可中断阻塞​​(如某些 I/O 操作):

java
Copy
public class IoThread extends Thread {  
    private volatile boolean stopped = false; // 自定义标志  
    private Socket socket;  

    @Override  
    public void run() {  
        while (!stopped && !isInterrupted()) {  
            socket.read(); // 不可中断的 I/O  
        }  
        socket.close(); // 安全释放资源  
    }  

    // 外部停止方法  
    public void cancel() {  
        stopped = true;  
        interrupt(); // 双保险:尝试中断阻塞  
    }  
}  

✅ ​​姿势4:线程池专属停止术(Future.cancel)​

用线程池时,直接操作 Future :

java
Copy
ExecutorService executor = Executors.newSingleThreadExecutor();  
Future<?> future = executor.submit(() -> {  
    while (!Thread.interrupted()) { /* 任务 */ }  
});  

// 优雅停止  
future.cancel(true); // true 表示发送中断信号  
executor.shutdown();  

🏭 ​​四、线程池如何停止线程?项目组解散流程​

​故事​​:老板(主线程)要解散项目组(线程池),需走标准流程:

  1. ​shutdown()​​:温柔通知

    java
    Copy
    executor.shutdown(); // 停止接收新任务,等现有任务完成  
    

    → 相当于通知项目组:“做完手头活就下班” 。

  2. ​shutdownNow()​​:紧急叫停

    java
    Copy
    List<Runnable> unfinished = executor.shutdownNow();  
    

    → 相当于:“所有人立刻停下手头工作!把没做完的报表(未执行任务)交上来!”
    ​底层​​:遍历工作线程,逐个调用 thread.interrupt()


💎 ​​五、总结:停止线程的黄金法则​

​场景​​正确做法​​错误做法​
循环任务while (!isInterrupted())stop()
阻塞中(sleep/wait)捕获 InterruptedException + 重置标志忽略异常
不可中断 I/Ovolatile标志 + interrupt()双保险只设标志不管阻塞
线程池任务future.cancel(true)直接操作底层 Thread

✨ ​​一句话精髓​​:​​线程的停止权应交由他人(其他线程),自己只需“听话”地检测信号并安全退出​​。这就是 Java 中断机制设计的哲学——​​协作式取消​​(Cooperative Cancellation) 。

最后送你一句源码箴言:

🧠 ​​“不要命令线程去死,而是请求它该死了,让它自己死得体面。”​​ —— Java 并发设计者的潜台词

搞懂了这套机制,你写的多线程代码就能像老工匠的车间一样:高效运转,优雅收场。