这里主要介绍interrupt的正确使用方式。
关于线程停止的常见的错误,请看上一篇错误的停止方式:两种常见错误
正确的停止方式:如何使用interrupt
正确的处理方式只有一个,那就是通过interrupt()
方法。下面分三种情况介绍如何正确使用Interrupt()
1. 没有阻塞函数的线程停止
这种情况比较简单,只需要在代码合适的位置检查线程是否中断即可。检测到中断后可以自己处理中断后的业务逻辑。
private static Runnable runnable = () -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("从银行卡扣掉此人1W元");
for (int j = 1; j < 11; j++) {
System.out.println("给了" + 1000 * j);
}
}
};
//停止线程
t.interrupt();
2. 有阻塞函数的线程停止
此处的阻塞函数是指会抛出InterruptedException
的相关函数,比如常见的wait()
、sleep()
,以及BlockingQueue的take()
、put()
等方法,有这类函数的线程停止要依照两个原则。
-
能抛出的就抛出
尽量在方法里抛出捕获
InterruptedException
而不是捕获后什么也不做。抛出异常的目的是为了尽量把异常的处理权交给上级调用者。 -
不抛出要恢复
如果不想抛出,或者有的方法不能抛出
InterruptedException
,那么要恢复中断。
通过代码来看下上面两个问题,简单的改动下上面的代码,将sleep封装为一个自己捕获异常的函数移出来。
private static Runnable runnable = () -> {
//检验线程是否中断
while (!Thread.currentThread().isInterrupted()) {
System.out.println("从银行卡扣掉此人1W元");
for (int j = 1; j < 11; j++) {
System.out.println("给了" + 1000 * j);
sleepWithoutException();
}
}
System.out.println("线程停止");
};
/**
* 不抛出异常的sleep,自己将异常捕获
*/
private static void sleepWithoutException() {
try {
//等待印钞机印钱
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(runnable);
t.start();
Thread.sleep(1000);
t.interrupt();
}
想象一下调用t.interrupt()
后线程会停止吗。运行后控制台的打印数据如下。
从银行卡扣掉此人1W元
...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
...
给了5000
给了6000
....
为什么调用了interrupt()
后线程依旧没有停止呢?与上面对应,答案呢,也分两点。
-
sleepWithoutException()
没有抛出向上传递异常,导致上层调用函数不知道线程已经停止了。不知道线程停止了,没法捕获自然也没法处理了。所以按照第一条原则就要把这个
InterruptedException
抛出,而不是自己吞了。按照这样把代码改成如下。private static Runnable runnable = () -> { try { while (!Thread.currentThread().isInterrupted()) { System.out.println("从银行卡扣掉此人1W元"); for (int j = 1; j < 11; j++) { System.out.println("给了" + 1000 * j); sleepThrowException(); } } } catch (InterruptedException e) { e.printStackTrace(); //todo 没给够钱的重新给 System.out.println("线程被中断了,没给够钱的重新给"); } System.out.println("线程停止"); }; /** * 抛出异常的sleep */ private static void sleepThrowException() throws InterruptedException { Thread.sleep(10); } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(runnable); t.start(); Thread.sleep(1000); t.interrupt(); }
运行后输出
... 给了2000 线程被中断了,没给够钱的重新给 线程停止 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at startthread.StopThread2.sleepThrowException(StopThread2.java:25) at startthread.StopThread2.lambda$static$0(StopThread2.java:11) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0
可见这时调用
t.interrupt();
后,线程如预期那样停止了。所以针对
InterruptedException
能抛出的就抛出。那么不能抛出或者实在不想抛出怎么办呢?下面就是第二种情况了。
-
由于
InterruptedException
会重置中断标志位,所以不能抛出的要恢复中断细心人肯定已经发现代码中有这么一行
while (!Thread.currentThread().isInterrupted())
那么为什么中断后还是没能停止呢,原来发生
InterruptedException
时,会重置线程的isInterrupted
标志位,所以上面while循环自然也不会跳出了。那么我们不想或者不能抛出异常时,就要恢复中断标志位。上面的代码改成这样就OK啦。
private static Runnable runnable = () -> { while (!Thread.currentThread().isInterrupted()) { System.out.println("从银行卡扣掉此人1W元"); for (int j = 1; j < 11; j++) { System.out.println("给了" + 1000 * j); sleepWithoutException(); } } System.out.println("线程停止"); }; /** * 不抛出异常的sleep,要恢复中断 */ private static void sleepWithoutException() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); //恢复中断 Thread.currentThread().interrupt(); } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(runnable); t.start(); Thread.sleep(1000); t.interrupt(); }
3. 有阻塞,但无法响应中断的线程停止
并不是所有的阻塞函数都能够响应中断的,比如常见的IO操作,ReentrantLock的lock()
等,这种的处理逻辑一般是在interrupt
的同时,根据场景进行相关的处理,比如关闭流等。