线程是“一段运行中的代码”,或者说是一个运行中的函数。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死? 答案:肯定是不能。
- 在Java中,有 stop()、destory() 之类的函数,但这些函数都是官方明确不建议使用的。
- 原因很简单,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等不能正常关闭。
- 因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知其退出。
线程中止:没有任何语言方面的需求一个被中断的线程应该终止
- 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断
- 在 java 中线程中断是一种协作机制,也就是说调用线程对象的 interrupt 方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己
线程中断的错误方法:jdk 不建议使用
- stop()
- destory()
线程中断的正确方法:
- interrupt()
- 使用标识位
错误的线程中止
1、stop():中止线程,并且清除线程监控器锁的信息,可能导致线程安全问题,jdk不建议使用
public class ThreadStopTest {class StopThread extends Thread {private int i, j;@Overridepublic 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);}}@Testpublic 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;@Overridepublic 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;@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()){System.out.println("线程正在运行!");long time = System.currentTimeMillis();// 使用while循环模拟sleepwhile ((System.currentTimeMillis() - time < SEQ)){}}System.out.println("线程收到中断请求进行中断!");}public static void main(String[] args) throws InterruptedException {final InterruptStop interruptStop = new InterruptStop();interruptStop.start();// 主线程休眠2sTimeUnit.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...");}}
