当前位置: 首页 > news >正文

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

学 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 不支持强行停线程。它只支持你告诉它"我想让你停",然后它自己安全地停。

http://www.jsqmd.com/news/1075934/

相关文章:

  • ROG Ally掌机性能优化终极指南:告别卡顿,尽享流畅游戏体验
  • 身份证遗失登报声明费用是多少?身份证遗失登报声明去哪办理?2026实测攻略
  • 江苏汉软 MES 软件核心应用场景与落地价值
  • ClickHouse:4.8 万 Star 的实时分析数据库
  • 终极指南:5分钟让Linux桌面自动化,告别重复点击
  • Python可执行文件逆向分析:深度解析pyinstaller和py2exe解包技术
  • 2026年,这些好用的皮带模组供应商,究竟有何独特魅力?
  • GitHub 狂揽 4万+ Star!这个项目直接让你省下 60–95% 的 Token
  • 如何快速找回加密压缩包密码:ArchivePasswordTestTool终极免费解决方案
  • 企业级AI编排实战:MuleSoft+LangChain混合架构落地指南
  • MechanicalSoup:让Python网页自动化更简单
  • GEO服务商怎么选?深圳本地的GEO服务商横向对比参考
  • AI Agent 中的向量数据库:深入解析与实战指南
  • 2026 Go语言高并发实战:用Gemini镜像站解决goroutine泄漏、channel死锁与性能分析
  • Midjourney V7实操指南:Personalization Profile与Draft Mode深度解析
  • Spring Boot 批量数据导入性能优化实战指南
  • 实战对比:OpenClaw直连 vs 挂载代理,采集成功率实测数据对比
  • Origin软件安装步骤(附安装包)Origin2025 超详细下载安装教程,科学绘图数据分析一步到位
  • 从CVE-2019-17558剖析Java反序列化漏洞:Log4j 1.x源码审计与实战复现
  • 遗传算法工程实战:从调参失效到工业级收敛的200行框架
  • OpCore Simplify:三步完成黑苹果配置的终极指南
  • 【极速入门数模电路】超高倍运算放大器
  • 高维数据降维可视化中决策边界的测度估计与几何分析
  • Hugging Face实战指南:Transformer微调、推理与部署全流程
  • AD7606C-18 国产替代 | 士模 CM2368|功耗降 30%、SNR 提升 2dB
  • ReAct Agent 完整实现:从零构建能查天气、算数学的智能助手
  • 安全性测评|2026年无畏契约账号平台TOP5
  • 留学党必看!Turnitin降AI率工具TOP5实测中英文论文AI率压到 10% 以下
  • Windows系统文件d3dx9d_33.dll丢失找不到问题解决
  • AI模型部署实践:从版权合规到实操验证