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

发布时间:2026/6/25 16:25:17
学了一周多线程,我终于搞懂了怎么“安全地“停掉一个线程 学 Java 多线程第一周遇到一个很尴尬的问题——线程开了代码在里面跑着然后我想让它停下来。翻书书上告诉我不要用 Thread.stop()这个方法已经被标记为废弃了。但是为什么呢为什么一个叫停止的方法不能用那用什么带着这个问题我搜了好几天。现在把学到的东西写下来。如果你也在学多线程可能用得上。第一个方法搞一个开关变量自己控制最直观的想法是这样的开一个循环在循环里检查一个变量变量变了就退出。publicclassSafeStopWithFlagimplementsRunnable{privatevolatilebooleanrunningtrue;Overridepublicvoidrun(){while(running){try{System.out.println(线程正在跑...);Thread.sleep(1000);}catch(InterruptedExceptione){runningfalse;Thread.currentThread().interrupt();}}System.out.println(线程安全停下来了。);}publicvoidstopThread(){runningfalse;}}这里有个坑我一开始没注意到running变量必须要加volatile。原因是两个线程可能在不同的 CPU 核心上跑不加 volatile 的话一个线程改了 running另一个线程可能看不见。举个例子你在主线程把 running 改成 false 了工作线程还在读自己缓存里的 running还是 true停不下来。volatile 解决的就是这个问题——保证一个线程改了另一个线程立刻能看到。用的时候也很简单SafeStopWithFlagtasknewSafeStopWithFlag();ThreadthreadnewThread(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(线程被中断信号停掉了。);}}调用的时候就这样ThreadthreadnewThread(newInterruptExample());thread.start();Thread.sleep(3000);thread.interrupt();// 就这一句这个办法一开始让我很困惑——为什么不直接用第一个方法的开关变量后来我理解了两者的区别如果线程正在 sleep 里面睡觉你用running false去停它它要等这一轮 sleep 结束、下一次循环才开始检查。而interrupt()可以直接把 sleep “叫醒”然后立刻响应。对于有阻塞操作的代码中断机制更及时。第三个方法用线程池的时候怎么办前面两个方法都是自己 new Thread()。但现在写项目大多用线程池ExecutorService。线程池的场景下你提交一个任务得到一个 Future 对象可以用它来取消任务。publicclassFutureCancelDemo{publicstaticvoidmain(String[]args){ExecutorServiceexecutorExecutors.newSingleThreadExecutor();Future?futureexecutor.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.serverSocketserverSocket;}Overridepublicvoidrun(){try{while(!Thread.currentThread().isInterrupted()){// 这行会一直等不接受 interruptSocketsocketserverSocket.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 不支持强行停线程。它只支持你告诉它我想让你停然后它自己安全地停。