线程是“一段运行中的代码”,或者说是一个运行中的函数。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死? 答案:肯定是不能。
- 在Java中,有 stop()、destory() 之类的函数,但这些函数都是官方明确不建议使用的。
- 原因很简单,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等不能正常关闭。
- 因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知其退出。
线程中止:没有任何语言方面的需求一个被中断的线程应该终止
- 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断
- 在 java 中线程中断是一种协作机制,也就是说调用线程对象的 interrupt 方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己
线程中断的错误方法:jdk 不建议使用
- stop()
- destory()
线程中断的正确方法:
- interrupt()
- 使用标识位
错误的线程中止
1、stop():中止线程,并且清除线程监控器锁的信息,可能导致线程安全问题,jdk不建议使用
public class ThreadStopTest {
class StopThread extends Thread {
private int i, j;
@Override
public void run() {
synchronized (this) {
// 增加同步锁,确保线程安全
i++;
try {
// 休眠10s,模拟耗时操作
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
public void print(){
System.out.println("i = " + i + "\nj = " + j);
}
}
@Test
public void test() throws InterruptedException {
final StopThread stopThread = new StopThread();
// 启动线程
stopThread.start();
// 休眠1s,确保变量i自增成功
TimeUnit.SECONDS.sleep(1);
// 暂停线程
stopThread.stop(); // 错误中止
// stopThread.interrupt(); // 正确中止
while (true){
// 确保线程已经中止
if (!stopThread.isAlive()) {
break;
}
}
// 输出结果
stopThread.print();
}
}
输出:
1、错误的线程中止:stopThread.stop();
2、正确的线程中止:stopThread.interrupt();
标识位中断
标识位中断:使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务
标识位中断的缺陷:如果线程在 while 循环中阻塞在某个地方,例如里面调用了 object.wait() 函数,那它可能永远没有机会再执行 while(!stopped) 代码,也就一直无法退出循环
public class FlagStop extends Thread {
// 标识位,volatile(标识该变量从内存中读取)保障可见性、有序性、读写的原子性
private volatile boolean stopped = false;
private final static long SEQ = 1000;
@Override
public void run() {
// 每隔一秒检测一下中断信号量
while (!stopped){
System.out.println("线程正在运行!");
long time = System.currentTimeMillis();
/**
* 使用while循环模拟sleep方法,这里不要使用sleep,否则在阻塞时会抛
* 出InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
* 失去了意义
*/
while ((System.currentTimeMillis() - time < SEQ)){}
}
System.out.println("线程收到关闭信号!");
}
public void stopFlag(){
this.stopped = true;
}
public static void main(String[] args) throws InterruptedException {
final FlagStop flagStop = new FlagStop();
flagStop.start();
// 主线程休眠2s后中止子线程
TimeUnit.SECONDS.sleep(2);
flagStop.stopFlag();
// 等待flagStop线程中止
flag.join();
}
}
thread.interrupt() 中断非阻塞状态
轻量级阻塞和重量级阻塞:
- 能够被中断的阻塞称为轻量级阻塞,对应的线程状态是 WAITING 或者 TIMED_WAITING
- synchronized 这种不能被中断的阻塞称为重量级阻塞,对应的状态是 BLOCKED
java 中断原理:java 的中断是一种协作机制,每个线程都有一个 boolean 的中断状态(该状态不在 Thread 的属性上),thread.interrupt() 方法只是将该状态设置为 true,然后程序自身需要不断去轮询标识位并做出处理,因此只有能抛出 InterruptedException 异常的方法才能被中断,对正常运行的线程调用 thread.interrupt() 并不会中止线程,只会改变标识位的状态
可中断的阻塞:只有声明了会抛出 InterruptedException 异常的函数才会抛出异常,一般这些方法都是可以被中断的,换言之,即可中断的方法会对 interrupt 方法的调用做出相应(例如 sleep 响应 interrupt 的操作包括清楚中断状态,抛出 InterruptedException),异常都是由可中断的方法自己抛出来的,而非由 interrupt 方法直接引起。以下为会抛出 InterruptedException 异常的函数:
- Thread 中的方法:
public static native void sleep(long millis) throws InterruptedException;
public final void join() throws InterruptedException(){}
- Object 中的方法:
public final void wait() throws InterruptedException{}
不可中断的阻塞:
- 不可中断的操作,包括进入 synchronized 段以及 Lock.lock(),inputSteam.read() 等,调用interrupt() 对于这几个方法无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去
interrupt 方法的作用:唤醒轻量级阻塞
- 如果目标线程在调用 Object 类的wait()、wait(long)或wait(long,int)等方法、join()、join(long,int) 或sleep(long,int)方法时被阻塞,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常,然后线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态
- 如果目标线程被 IO 或者 NIO 中 Channel 所阻塞,同样,IO 操作会被中断或者返回特殊异常值,达到终止线程的目的
- 对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)
- 以上条件都不满足,则会设置此线程的中断状态
中断后异常处理:
- 直接抛出而非 try 来捕获
- 在 catch 子句中重新设置中断状态 Thread.currentThread.interrupt(); ,让外界通过判断 Thread.currentThread.isInterrupted() 标识来决定线程是否继续
检测线程是否被中断:Thread类的方法
- static boolean interrupted:测试当前线程是否已经中断,会清除中断状态,即先返回当前线程的中断状态,然后将 interrupt 标识设为 false
- boolean isInterrupted():测试线程是否已经中断,只会获取,不会清除中断标识【建议使用此方法】
基本使用:
Runnable runnable = ()->{
try {
TimeUnit.SECONDS.sleep(2);
/**
* 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
* !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
* 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时
*/
while (!Thread.currentThread().isInterrupted()){
// 如果调用了阻塞方法但没有阻塞时的处理
....
}
} catch (InterruptedException e) {
// 线程在wait或者sleep期间被中断,进行处理
e.printStackTrace();
} finally {
// 线程结束前做一些清理工作
System.out.println("资源释放等!");
}
};
上述的 while 循环在 try 块里,如果 try 在 while 循环里时,因该在 catch 块里重新设置一下中断标示,因为抛出 InterruptedException 异常后,中断标示位会自动清除
Runnable runnable = ()->{
while(!Thread.currentThread().isInterrupted()){
try{
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// 重新设置中断标识
Thread.currentThread().interrupt();
}
}
};
使用 thread.interrupt() 中断非阻塞线程:
public class InterruptStop extends Thread{
private static final long SEQ = 1000;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程正在运行!");
long time = System.currentTimeMillis();
// 使用while循环模拟sleep
while ((System.currentTimeMillis() - time < SEQ)){}
}
System.out.println("线程收到中断请求进行中断!");
}
public static void main(String[] args) throws InterruptedException {
final InterruptStop interruptStop = new InterruptStop();
interruptStop.start();
// 主线程休眠2s
TimeUnit.SECONDS.sleep(2);
// 发出中断请求
interruptStop.interrupt();
// 等待子线程结束
interruptStop.join();
}
}
thread.interrupt() 方法本质:唤醒轻量级阻塞,即将线程的中断标识设置为 true ,在线程收到轻量级阻塞的地方抛出一个异常InterruptedException,并且中断状态也将被清除(设置为false),这样线程就得以退出阻塞的状态
class Example3 extends Thread {
public static void main(String args[]) throws Exception {
Example3 thread = new Example3();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
thread.interrupt();// 等中断信号量设置后再调用
Thread.sleep(3000);
System.out.println("Stopping application...");
}
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread running...");
try {
/*
* 如果线程阻塞,将不会去检查中断信号量stop变量,所以thread.interrupt()
* 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
* 进行异常块进行 相应的处理
*/
Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
} catch (InterruptedException e) {
System.out.println("Thread interrupted...");
/*
* 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
* 过程中受阻,则其中断状态将被清除
*/
System.out.println(this.isInterrupted());// false
//中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
//不需要,则不用调用
Thread.currentThread().interrupt();
}
}
System.out.println("Thread exiting under request...");
}
}