这里主要介绍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的同时,根据场景进行相关的处理,比如关闭流等。
















