尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

学了一周多线程,我终于搞懂了怎么“安全地“停掉一个线程

学了一周多线程,我终于搞懂了怎么“安全地“停掉一个线程
📅 发布时间:2026/6/25 16:42:02

学 Java 多线程第一周,遇到一个很尴尬的问题——线程开了,代码在里面跑着,然后我想让它停下来。翻书,书上告诉我:不要用 Thread.stop(),这个方法已经被标记为废弃了。

但是为什么呢?为什么一个叫"停止"的方法不能用?那用什么?

带着这个问题我搜了好几天。现在把学到的东西写下来。如果你也在学多线程,可能用得上。


第一个方法:搞一个开关变量,自己控制

最直观的想法是这样的:开一个循环,在循环里检查一个变量,变量变了就退出。

publicclassSafeStopWithFlagimplementsRunnable{privatevolatilebooleanrunning=true;@Overridepublicvoidrun(){while(running){try{System.out.println("线程正在跑...");Thread.sleep(1000);}catch(InterruptedExceptione){running=false;Thread.currentThread().interrupt();}}System.out.println("线程安全停下来了。");}publicvoidstopThread(){running=false;}}

这里有个坑我一开始没注意到:running变量必须要加volatile。原因是两个线程可能在不同的 CPU 核心上跑,不加 volatile 的话,一个线程改了 running,另一个线程可能"看不见"。

举个例子:你在主线程把 running 改成 false 了,工作线程还在读自己缓存里的 running,还是 true,停不下来。

volatile 解决的就是这个问题——保证一个线程改了,另一个线程立刻能看到。

用的时候也很简单:

SafeStopWithFlagtask=newSafeStopWithFlag();Threadthread=newThread(task);thread.start();// 过一会,让它停Thread.sleep(3000);task.stopThread();

这个办法的好处是简单,坏处是如果线程正在做 sleep 或者 wait 这种阻塞操作,你改了 running 它也不会立刻知道——它要等阻塞结束、下一次循环检查的时候才退出。


第二个方法:用 Java 自带的中断机制

学到这里我遇到了第二个办法,也是 Java 官方推荐的做法。说实话我一开始没太理解,感觉和第一个方法差不多。

Java 的每个线程都有一个"中断标志位",就是一个布尔值。中断机制说白了就是:

  • 调用thread.interrupt()— 把标志位设成 true
  • 线程自己去检查Thread.currentThread().isInterrupted()— 看看标志位是不是 true
  • 如果是,就主动退出

说起来很简单。但有个地方特别绕:

有一些方法(比如 sleep()、wait()、join())在等待的时候会去检查这个中断标志。如果它们发现中断了,会做两件事:抛出InterruptedException,并且把中断标志清掉(重新设成 false)。

这就导致了一个很反直觉的情况:

// 想象一下这个场景thread.interrupt();// 我设置了中断// 此时 interrupt 标志是 trueThread.sleep(1000);// sleep 发现标志是 true// 抛出 InterruptedException// 把标志清回 false

所以当你捕获到InterruptedException之后,如果还想让上层的代码知道"我被中断了",就得在 catch 里再调用一次 interrupt():

publicclassInterruptExampleimplementsRunnable{@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){try{System.out.println("工作中...");Thread.sleep(1000);}catch(InterruptedExceptione){// sleep 被中断的时候,中断标志已经被清掉了// 所以这里要重新设回来System.out.println("睡觉的时候被叫醒了!");Thread.currentThread().interrupt();break;}}System.out.println("线程被中断信号停掉了。");}}

调用的时候就这样:

Threadthread=newThread(newInterruptExample());thread.start();Thread.sleep(3000);thread.interrupt();// 就这一句

这个办法一开始让我很困惑——为什么不直接用第一个方法的开关变量?后来我理解了两者的区别:

如果线程正在 sleep 里面睡觉,你用running = false去停它,它要等这一轮 sleep 结束、下一次循环才开始检查。而interrupt()可以直接把 sleep “叫醒”,然后立刻响应。对于有阻塞操作的代码,中断机制更及时。


第三个方法:用线程池的时候怎么办

前面两个方法都是自己 new Thread()。但现在写项目大多用线程池(ExecutorService)。线程池的场景下,你提交一个任务,得到一个 Future 对象,可以用它来取消任务。

publicclassFutureCancelDemo{publicstaticvoidmain(String[]args){ExecutorServiceexecutor=Executors.newSingleThreadExecutor();Future<?>future=executor.submit(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("任务运行中...");try{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("任务被中断了。");Thread.currentThread().interrupt();}}});try{Thread.sleep(3000);future.cancel(true);// true 表示发送中断信号}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{executor.shutdown();}}}

其实关键就是future.cancel(true)这一句。传 true 会在底层调用线程的 interrupt() 方法。所以任务代码里还是需要正确处理中断才能配合上。


第四个方法:碰到不接受中断的操作怎么办

学到前面三个方法的时候,我以为我已经全懂了。直到我遇到了ServerSocket.accept()——这个方法会一直阻塞着等客户端连接,但它不听 interrupt() 的信号。

我试过:

// 一个线程在 accept 那里等着// 另一个线程去 interrupt() 它// 结果:它不动。interrupt 电不到它。

这就很让人头疼。网上查了一下,很多人说这是"不可中断的阻塞操作"。传统的 I/O 操作和 synchronized 锁都属于这一类。

那怎么办?

思路是这样的:既然你电不动它,我就把它的资源直接关了。资源一关,阻塞操作会抛出 IOException,线程就能响应了。

publicclassSocketHandlerimplementsRunnable{privatefinalServerSocketserverSocket;publicSocketHandler(ServerSocketserverSocket){this.serverSocket=serverSocket;}@Overridepublicvoidrun(){try{while(!Thread.currentThread().isInterrupted()){// 这行会一直等,不接受 interruptSocketsocket=serverSocket.accept();// 处理连接...}}catch(IOExceptione){if(Thread.currentThread().isInterrupted()){System.out.println("资源被关了,线程停下来了。");}}}publicvoidstopThread(){Thread.currentThread().interrupt();try{serverSocket.close();// 关键:关掉资源}catch(IOExceptione){System.err.println("关闭 socket 出错: "+e);}}}

这个办法让我觉得,多线程编程有时候不是"写好代码就行",还得理解底层阻塞的原理,知道哪些操作响应中断、哪些不响应。


最后说一下:那些别用的方法

说实话,一开始我在网上搜的时候,确实看到过Thread.stop()。用起来超级简单,一行代码线程就死了。但我为什么不能用它呢?

我看了很多文章才理解里面有什么坑:

Thread.stop()会在线程执行的任意位置强行终止它,然后立即释放它持有的所有锁。释放锁听起来是好事,但问题是,stop 不会管这个对象在释放锁的时候处于什么状态。举个例子:你正在往一个 List 里加数据,加到一半,stop 来了,数据还没加完,锁被释放了。这时候别的线程读到这个 List,看到的是半个数据——不完整、不一致。这种问题很难排查。

Thread.suspend()和Thread.resume()是相反的问题——挂起的时候不释放锁,如果拿到锁的线程被挂起了,其他线程等着这把锁,整个程序就死锁了。

所以它们被废弃是有理由的。虽然用起来方便,但不能碰。


简单总结

场景怎么做
线程里就是纯计算,没有阻塞操作加个 volatile 变量当开关
有 sleep、wait 这类操作用 interrupt() 来停
用线程池管理任务用 Future.cancel(true)
遇到传统 I/O 等阻塞关掉资源让它抛异常

学完这些我最大的感受是:线程的停止不是"杀死",而是"协商"。你告诉它该停了,它自己决定什么时候整理好东西停下来。这不是 Java 的设计缺陷,恰恰是它安全的原因。

如果有人问我 Java 里怎么停掉一个线程,我现在会说:Java 不支持强行停线程。它只支持你告诉它"我想让你停",然后它自己安全地停。

相关新闻

  • 2026年,这些好用的皮带模组供应商,究竟有何独特魅力?
  • 2026 Go语言高并发实战:用Gemini镜像站解决goroutine泄漏、channel死锁与性能分析
  • Origin软件安装步骤(附安装包)Origin2025 超详细下载安装教程,科学绘图数据分析一步到位

最新新闻

  • 超小可执行文件再探:从45字节到76字节,合规与精简的艰难平衡!
  • PHP反序列化漏洞:从原理到实战利用与防御
  • 【VMware部署GitLab终极指南】:20年运维专家亲授高可用架构设计与避坑清单
  • Django毕设选题推荐:基于 Django 的智能化就业信息发布推荐系统设计与实现 基于 Django 的高校就业数据智能推荐管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 免部署的AI教学平台哪家性价比高?看实战云的SaaS模式
  • FMPy:工业级FMU仿真引擎的Python实现

日新闻

  • 利用微PE工具箱进行系统安装教程
  • 渗透测试十大核心工具实战指南:从信息搜集到报告生成全流程解析
  • 暗黑破坏神2存档编辑器:网页版角色修改工具完全指南

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号