Java中的线程在运行的时候,有办法强制让它中断运行么?在JDK的Thread类中有stop和destroy方法,但是已经被标记为过时的,stop方法之所以不推荐使用是因为它是不安全的,比如它会让线程释放它持有的锁,这样可能会导致它保护的资源处于不一致的状态,destroy方法则直接抛出NoSuchMethodError异常。
正确的、优雅的中断线程的方式应该是给线程发出中断信号(即调用线程实例的interrupt方法,用余春龙大佬的话来说,interrupt这个词很容易让人产生误解,从字面含义看好像是线程运行到一半就把它中断了,其实不是的,调用线程实例的interrupt方法,只是向线程发出了中断信号,至于中断信号是否以抛出InterruptedException的方式表现出来要看线程是否处于轻量级阻塞状态,轻量级阻塞状态下文有解释),然后线程正确处理这个中断信号;或者线程内部自己去检查某个终止标志(如某个volatile的boolean值)来决定是否停止运行。
让线程检查boolean条件从而优雅退出package thread;
public class ThreadExitDemo {
private static volatile boolean closeFlag = false;
public static void main(String[] args) throws Exception{
Thread thread = new Thread(() -> {
while (!closeFlag) {
try {
//模拟需要循环处理的任务,比如一直不断地从数据库中取数据出来处理
Thread.sleep(1000);
System.out.println("thread is running");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread will be closed");
});
thread.start();
Thread.sleep(3000);
closeFlag = true;
}
}
这种方式有个缺陷,就是如果没有在一个循环中处理的逻辑,就没办法反复检查是否该停止线程。这个时候,就需要线程的中断机制了。
通过调用线程的interrupt方法来中断线程package thread;
public class ThreadInterruptDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(()->{
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
System.out.println("thread is interrupted");
e.printStackTrace();
}
}
});
thread.start();
System.out.println("interrupt thread");
// 如果不调用interrupt,线程thread会一直阻塞在wait方法,除非有线程调用lock的notify唤醒它
thread.interrupt();
}
}
通过调用Thread的interrupt方法,可以给线程发出中断信号。给线程发出中断信号的时候,线程所处状态不同,表现的行为也不同,当线程处在WAITING或TIMED_WAITING状态的时候,会抛出InterruptedException,并且会被唤醒(上边的例子中就是通过interrupt让thread从wait中醒过来);如果线程处在BLOCKED状态则不会抛出InterruptedException;如果线程处在运行状态,也不会抛出中断异常,但是可以通过检查线程的中断状态来判断它是否收到过中断信号。
哪些方法在线程被中断时会抛出InterruptedException?Thread.sleep()、Thread.join()、Object.wait()等在方法签名上有throws InterruptedException的方法在被中断的时候会抛出中断异常。这些方法调用会让线程进入WAITING或TIMED_WAITING状态,这些状态是轻量级的阻塞。
可以中断synchronized代码块么?答案是不可以,如下所示,程序会一直处在运行状态,调用了interrupt也无法中断线程。只能等待线程自己运行完。
package thread;
public class SynchronizedInterruptDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(()->{
synchronized (lock) {
while (flag) {
int a = 1;
a++;
}
}
System.out.println("after synchronized block");
});
thread.start();
thread.interrupt();
}
}
这个时候就可以通过线程自己检查是否收到过中断来跳出循环了。
线程内部自己检查是否收到了中断信号在上面的例子中,增加Thread.interrupted检查,可以判断出线程是否收到过中断请求,如果收到过,则线程可以自己跳出循环,优雅结束自己。
package thread;
public class SynchronizedInterruptDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(()->{
synchronized (lock) {
while (flag) {
int a = 1;
a++;
if (Thread.interrupted()) {
break;
}
}
}
System.out.println("after synchronized block");
});
thread.start();
thread.interrupt();
}
}
Thread中的isInterrupted方法与interrupted方法区别
Thread类中有一个静态方法interrupted还有一个实例方法isInterrupted,前者在读取当前线程中断状态后会清除中断状态,不可重复读,后者读取中断状态后不会清除,可以重复读。
一个常见的中断状态坑在线程处于轻量级阻塞的时候,给线程发出中断信号的时候,线程内部抛出了InterruptedException后,再读取线程中断状态,会发现状态不是中断。其实中断状态和中断异常都是线程内部代码感知自己是否收到中断信号的方式,给了中断异常就不给中断状态了,给了中断状态就不给中断异常了。
package thread;
public class InterruptedExceptionDemo {
public static void main(String[] args) throws Exception{
Thread thread = new Thread(()->{
while (true) {
if (Thread.interrupted()) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("当前线程是否中断了:"+ Thread.interrupted());
e.printStackTrace();
}
}
});
thread.start();
//为了演示效果,这儿故意睡眠了100毫秒,为了让中断信号被sleep以异常形势抛出,而不是直接break
Thread.sleep(100);
thread.interrupt();
}
}
将中断异常转换成中断状态,就可以保证能及时检测到中断信号了:
package thread;
public class InterruptedExceptionDemo {
public static void main(String[] args) throws Exception{
Thread thread = new Thread(()->{
while (true) {
if (Thread.interrupted()) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("当前线程是否中断了:"+ Thread.interrupted());
//将中断异常转换成中断状态,这样上面的if就可以检测到中断了
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
});
thread.start();
//为了演示效果,这儿故意睡眠了100毫秒,为了让中断信号被sleep以异常形势抛出,而不是直接break
Thread.sleep(100);
thread.interrupt();
}
}
一个可能不符合你认知的例子
好了,java线程的中断基本上都介绍完了,下面来一个例子巩固一下。
package thread;
import java.time.LocalDateTime;
public class WaitTest {
public static void main(String[] args) throws Exception{
Object lock = new Object();
Thread t1 = new Thread(()->{
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
// 输出语句2
System.out.println("catch InterruptedException at: " + LocalDateTime.now());
}
}
});
Thread t2 = new Thread(()->{
synchronized (lock){
try {
//睡眠5秒钟
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
//为了确保t2在t1之后启动,这儿睡一会
Thread.sleep(100);
t2.start();
// 输出语句1
System.out.println("interrupt t1 at: " + LocalDateTime.now());
t1.interrupt();
}
}
请问上面程序运行后,输出语句2打印的时间与输出语句1打印的时间相隔多久?
答案是5秒。
因为lock.wait()会让t1线程进入轻量级的阻塞状态(WAITING状态),wait会让t1释放锁lock,然后t2线程拿到了lock锁,睡眠5秒钟,在t2线程start后,主线程就给t1线程发出了中断信号,这个时候t1线程就被唤醒了(另一种唤醒t1线程的方式是调用notify方法),但是t2线程睡眠5秒钟还没结束,t1即使被唤醒也要等拿到lock锁之后才可以接着执行lock.wait()调用后的代码,等t2睡眠5秒结束后,t1拿到lock锁,发现自己在WAITING的时候收到中断信号了,所以抛出一个InterruptedException。
参考资料:
1.余春龙《Java并发实现原理 JDK源码剖析》
2.银文杰《Java高并发与集合框架 JCF和JUC源码分析与实现》



