转自:http://ibruce.info/2013/12/19/how-to-stop-a-java-thread/
与此问题相关的内容主要涉及三部分:已废弃的Thread.stop()、迷惑的thread.interrupt系列、最佳实践Shared Variable。
已废弃的Thread.stop()
@Deprecated public final void stop() { stop(new ThreadDeath()); }
如上是Hotspot JDK 7中的java.lang.Thread.stop()的代码,学习一下它的doc:
该方法天生是不安全的。使用thread.stop()停止一个线程,导致释放(解锁)所有该线程已经锁定的监视器(因沿堆栈向上传播的未检查异常ThreadDeath而解锁)。如果之前受这些监视器保护的任何对象处于不一致状态,则不一致状态的对象(受损对象)将对其他线程可见,这可能导致任意的行为。
是不是差点被这段话绕晕,俗点说:目标线程可能持有一个监视器,假设这个监视器控制着某两个值之间的逻辑关系,如var1必须小于var2,某一时刻var1等于var2,本来应该受保护的逻辑关系,不幸的是此时恰好收到一个stop命令,产生一个ThreadDeath错误,监视器被解锁。这就导致逻辑错误,当然这种情况也可能不会发生,是不可预料的。注意:ThreadDeath是何方神圣?是个java.lang.Error,不是java.lang.Exception。
public class ThreadDeath extends Error { private static final long serialVersionUID = -4417128565033088268L; }
thread.stop()方法的许多应用应该由“只修改某些变量以指示目标线程应该停止”的代码取代。目标线程应周期性的检查该变量,当发现该变量指示其要停止运行,则退出run方法。如果目标线程等待很长时间,则应该使用interrupt方法中断该等待。
其实这里已经暗示停止一个线程的最佳方法:条件变量 或 条件变量+中断。
更多请查看:
Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?
上文请参考我的翻译xxx。
其它关于stop方法的doc:
- 该方法强迫停止一个线程,并抛出一个新创建的ThreadDeath对象作为异常。
- 停止一个尚未启动的线程是允许的,如果稍后启动该线程,它会立即终止。
- 通常不应试图捕获ThreadDeath,除非它必须执行某些异常的清除操作。如果catch子句捕获了一个ThreadDeath对象,则必须重新抛出该对象,这样该线程才会真正终止。
小结:
Thread.stop()不安全,已不再建议使用。
令人迷惑的thread.interrupt()
Thread类中有三个方法会令新手迷惑,他们是:
public void Thread.interrupt() // 无返回值 public boolean Thread.isInterrupted() // 有返回值 public static boolean Thread.interrupted() // 静态,有返回值
如果按照近几年流行的重构,代码整洁之道,程序员修炼之道等书的观点,这几个方法的命名相对于其实现的功能来说,不够直观明确,极易令人混淆,是低级程序猿的代码。逐个分析:
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
中断本线程。无返回值。具体作用分以下几种情况:
- 如果该线程正阻塞于Object类的wait()、wait(long)、wait(long, int)方法,或者Thread类的join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)方法,则该线程的中断状态将被清除,并收到一个java.lang.InterruptedException。
- 如果该线程正阻塞于interruptible channel上的I/O操作,则该通道将被关闭,同时该线程的中断状态被设置,并收到一个java.nio.channels.ClosedByInterruptException。
- 如果该线程正阻塞于一个java.nio.channels.Selector操作,则该线程的中断状态被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用java.nio.channels.Selector.wakeup()方法一样。
- 如果上述条件都不成立,则该线程的中断状态将被设置。
小结:第一种情况最为特殊,阻塞于wait/join/sleep的线程,中断状态会被清除掉,同时收到著名的InterruptedException;而其他情况中断状态都被设置,并不一定收到异常。
中断一个不处于活动状态的线程不会有任何作用。如果是其他线程在中断该线程,则java.lang.Thread.checkAccess()方法就会被调用,这可能抛出java.lang.SecurityException。
public static boolean interrupted() { return currentThread().isInterrupted(true); }
检测当前线程是否已经中断,是则返回true,否则false,并清除中断状态。换言之,如果该方法被连续调用两次,第二次必将返回false,除非在第一次与第二次的瞬间线程再次被中断。如果中断调用时线程已经不处于活动状态,则返回false。
public boolean isInterrupted() { return isInterrupted(false); }
检测当前线程是否已经中断,是则返回true,否则false。中断状态不受该方法的影响。如果中断调用时线程已经不处于活动状态,则返回false。
interrupted()与isInterrupted()的唯一区别是,前者会读取并清除中断状态,后者仅读取状态。
在hotspot源码中,两者均通过调用的native方法isInterrupted(boolean)来实现,区别是参数值ClearInterrupted不同。
private native boolean isInterrupted(boolean ClearInterrupted);
经过上面的分析,三者之间的区别已经很明确,来看一个具体案例,是我在工作中看到某位架构师的代码,只给出最简单的概要结构:
public void run() { while(!Thread.currentThread().isInterrupted()) { try { Thread.sleep(10000L); ... //为篇幅,省略其它io操作 ... //为简单,省略其它interrupt操作 } catch (InterruptedException e) { break; } } }
我最初被这段代码直接绕晕,用thread.isInterrupted()方法作为循环中止条件可以吗?
根据上文的分析,当该方法阻塞于wait/join/sleep时,中断状态会被清除掉,同时收到InterruptedException,也就是接收到的值为false。上述代码中,当sleep之后的调用otherDomain.xxx(),otherDomain中的代码包含wait/join/sleep并且InterruptedException被catch掉的时候,线程无法正确的中断。
因此,在编写多线程代码的时候,任何时候捕获到InterruptedException,要么继续上抛,要么重置中断状态,这是最安全的做法,参考『Java Concurrency in Practice』。凡事没有绝对,如果你可以确保一定没有这种情况发生,这个代码也是可以的。
下段内容引自:『Java并发编程实战』 第5章 基础构建模块 5.4 阻塞方法与中断方法 p77
当某个方法抛出InterruptedException时,表示该方法是一个阻塞方法。当在代码中调用一个将抛出InterruptedException异常的方法时,你自己的方法也就变成了一个阻塞方法,并且必须要处理对中断的相应。对于库代码来说,有两种选择:
- 传递InterruptedException。这是最明智的策略,将异常传递给方法的调用者。
- 恢复中断。在不能上抛的情况下,如Runnable方法,必须捕获InterruptedException,并通过当前线程的interrupt()方法恢复中断状态,这样在调用栈中更高层的代码将看到引发了一个中断。如下代码是模板:
public void run() { try { // ① 调用阻塞方法 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // ② 恢复被中断的状态 } }
最后再强调一遍,②处的 Thread.currentThread().interrupt() 非常非常重要。
最佳实践:Shared Variable
不记得哪本书上曾曰过,最佳实践是个烂词。在这里这个词最能表达意思,停止一个线程最好的做法就是利用共享的条件变量。
对于本问题,我认为准确的说法是:停止一个线程的最佳方法是让它执行完毕,没有办法立即停止一个线程,但你可以控制何时或什么条件下让他执行完毕。
通过条件变量控制线程的执行,线程内部检查变量状态,外部改变变量值可控制停止执行。为保证线程间的即时通信,需要使用使用volatile关键字或锁,确保读线程与写线程间变量状态一致。下面给一个最佳模板:
/** * @author bruce_sha (bruce-sha.github.io) * @version 2013-12-23 */ public class BestPractice extends Thread { private volatile boolean finished = false; // ① volatile条件变量 public void stopMe() { finished = true; // ② 发出停止信号 } @Override public void run() { while (!finished) { // ③ 检测条件变量 // do dirty work // ④业务代码 } } }
本文尚未完成,请耐心等待。
当④处的代码阻塞于wait()或sleep()时,线程不能立刻检测到条件变量。因此②处的代码最好同时调用interrupt()方法。
小结:
How to Stop a Thread or a Task ? 详细讨论了如何停止一个线程, 总结起来有三点:
- 使用violate boolean变量来标识线程是否停止。
- 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性。
- 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO。
总结:
要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有提供任何机制来安全地终止线程。但它提供了中断(Interruption),这是一种协作机制,能够使一个线程终止另一个线程的的工作。—— 『Java并发编程实战』 第7章 取消与关闭 p111
中断是一种协作机制。一个线程不能强制其它线程停止正在执行的操作而去执行其它的操作。当线程A中断B时,A仅仅是要求B在执行到某个可以暂停的地方停止正在执行的操作——前提是如果线程B愿意停下来。—— 『Java并发编程实战』 第5章 基础构建模块 p77
总之,中断只是一种协作机制,需要被中断的线程自己处理中断。停止一个线程最佳实践是中断 + 条件变量。
相关推荐
在Java中,中断一个正在运行的线程可以使用共享变量发出信号,告诉线程停止正在运行的任务。线程周期性的核查这一变量(尤其在冗余操作期间),然后有秩序地中止任务。这是中断线程最好的、最受推荐的方式。 ...
Java提供了一个更安全的中断线程的机制,即`Thread.interrupt()`。当调用`interrupt()`时,目标线程的中断状态会被设置,线程在检查到中断状态后可以决定如何响应。例如,`Thread.sleep()`、`SocketInputStream....
然而,如何优雅地停止一个正在运行的线程却是一个复杂且容易出错的过程。本文将深入探讨Java线程停止的方法,特别是通过一个示例代码片段来解析如何利用标志变量(Flag)控制线程的生命周期,以及这种方法背后的原理...
Java线程是Java编程中非常重要的一个概念,它可以帮助开发者实现多任务并行处理,提高程序的执行效率。理解线程的创建、生命周期管理以及线程间的同步和通信机制对于开发高质量的Java应用至关重要。希望以上内容能够...
1. 提高程序的执行效率:多线程编程可以将复杂的任务分解成多个小任务,每个任务都可以由一个独立的线程来执行,从而提高程序的执行效率。 2. 提高程序的响应速度:多线程编程可以让程序能够快速地响应用户的输入和...
Java线程是Java编程中的重要概念,特别是在多核处理器和并发处理中不可或缺。Java线程允许程序在同一时间执行多个不同的任务,从而提高了程序的效率和响应性。在燕山大学信息学院计算机系的课程中,李峰教授讲解了...
线程是程序中一个单一的顺序控制流,它在程序的上下文中运行,但具有独立的执行路径。多线程则是指在单个程序内同时运行多个不同的线程,每个线程执行不同的任务。线程共享同一份程序内存空间,但拥有各自的程序...
Java线程有10个优先级(MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY),默认优先级是NORM_PRIORITY。但是,线程优先级并不保证绝对的执行顺序,操作系统调度策略可能影响实际执行顺序。 7. join()方法: 一个线程...
在Java编程环境中,单线程指的是程序执行过程中只有一个线程在运行。这意味着任何时刻只能执行一个任务,上一个任务完成后才会进行下一个任务。单线程模型简化了程序设计,降低了程序复杂度,使得开发者可以更专注于...
Java线程状态流转图是一种用于描述Java线程生命周期中不同的状态和状态转换的图形表示方式。该图形展示了Java线程从创建到终止的整个生命周期,并详细介绍了每种状态的特点和转换规则。 NEW(初始化状态) 在Java...
Java线程是Java编程语言中的核心概念,尤其在多任务处理和并发编程中扮演着重要角色。线程允许一个程序内部同时执行多个独立的控制流,使得程序能够更高效地利用处理器资源。本文将深入解析Java线程的相关知识点,...
Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...
Java的TDA线程转储分析器是一个用于分析Sun Java VM生成的线程转储和堆信息的小型Swing GUI(目前用1.4测试)。它从提供的日志文件中解析线程转储和类直方图。它提供关于发现的线程转储的统计信息,提供关于锁定监视器...
TDA(Thread Dump Analyzer)是一款强大的Java线程分析工具,它能够帮助开发者深入理解应用在运行时的线程状态,包括线程的阻塞情况、等待资源、死锁等问题。下面将详细介绍TDA的使用方法、功能以及它如何帮助我们...
如果一个线程在运行过程中抛出未捕获的异常,那么该线程将被终止。我们可以使用`uncaughtExceptionHandler`来处理线程中抛出的未捕获异常。 总的来说,"JAVA线程学习(源代码)"涵盖了Java线程的基础知识和高级特性,...
Java线程有五种状态:新建、可运行、运行、阻塞和终止。可以通过Thread类的getState()方法查看线程状态。线程的控制包括: - sleep():使当前线程进入休眠状态,指定时间后自动唤醒。 - join():让当前线程等待另一...
### Java线程培训资料知识点详解 #### 一、Java线程基本概念 1. **如何编写与启动线程** - **方式一:继承Thread类** ```java class MyThread extends Thread { @Override public void run() { // 业务逻辑 ...
本示例提供了可以直接运行的Java多线程代码,帮助开发者更好地理解和运用多线程。 一、继承Thread类 在Java中,可以通过创建一个新的类来继承Thread类,然后覆盖其run()方法,将需要并发执行的任务放入run()方法中...
Java线程是Java编程语言中的一个核心概念,它允许程序同时执行多个任务,极大地提高了程序的并发性和效率。本教程将深入探讨Java线程的使用,帮助开发者掌握这一关键技术。 一、线程基础 1. **线程的概念**:线程...
Java线程是并发编程的重要组成部分,它允许程序同时执行多个任务,从而充分利用系统资源,提高程序的效率和响应性。这本书详细介绍了Java线程的各个方面,包括基础知识、高级特性以及实战应用。 在Java中,线程是...