本文由浅入深介绍了中断机制、中断的常见案例和使用场景。
1. 为什么需要
因为一些原因需要取消原本正在执行的线程。我们举几个栗子:
- 假设踢足球点球时,A队前4轮中了4个球,B队前4轮只中了2个球,此时胜负已分,第5轮这个点球就不用踢了,此时需要停止A队的线程和B队的线程(共5个点球)。
- 假设用户从网络上下载一个50M的文件,如果网络很慢,用户等的不耐烦,点了取消下载操作,此时需要停止下载的线程。
2. 如何理解
中断可以理解为线程的一个内部标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。
中断就是其他线程通过interrupt
方法给该线程发一个信号,该线程收到信号后可以选择响应或不响应信号,中断它只是一种协商机制。响应信号通常会结束自身run()
方法的执行退出线程,不响应则继续执行无任何影响。
线程通过检测自身是否被中断来进行响应,检测方法有2种,分别是public static boolean interrupted()
和public boolean isInterrupted()
,静态的方法会清除中断标识位属性,非静态方法不会清除中断标识位属性。
3. 如何使用
3.1. 中断相关API
Thread源码中关于中断的方法:
public class Thread implements Runnable {
// 仅仅只是设置中断状态
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
// 会清除中断状态
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 不会清除中断状态
public boolean isInterrupted() {
return isInterrupted(false);
}
private native void interrupt0();
private native boolean isInterrupted(boolean ClearInterrupted);
}
绘制成表格:
API | 作用 |
---|---|
public void interrupt() | 中断this线程 |
public static boolean interrupted() | 测试当前线程是否被中断,且线程的中断状态会被清除 |
public boolean isInterrupted() | 测试this线程是否中断。 中断状态不会被这个方法影响 |
特别需要注意的是:静态interrupted方法会清除线程的中断状态。
3.2. 中断正常状态下的线程
中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()
方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
public class Main extends Thread {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted()) {
i++;
System.out.println();
}
System.out.println("done");
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Main();
t.start();
t.sleep(1000);
t.interrupt();
}
}
main
线程通过调用t.interrupt()
中断t
线程。但需要注意,interrupt()
方法仅仅是给t
线程发送了"中断请求",只是把t
线程的标识属性设置为中断,至于t
线程是否响应或处理该中断请求则要看t
线程的具体代码。
这里,t
线程在while中循环检测中断请求(isInterrupted()
),如果检测到线程被中断则跳出while循环结束线程的run()
方法。
3.3. 中断特殊状态下的线程
特殊状态其实在官网Thread中有好几种,这里只列举最常见的一种
如果线程
- 被阻塞在Object类的
wait()
,wait(long)
,wait(long, int)
方法 - 或被阻塞在Thread类的
join()
,join(long)
,join(long, int)
,sleep(long)
,sleep(long, int)
如果此时调用线程的中断方法interrupt()
,线程的中断状态会被清除且抛出InterruptedException
异常。这种情况的一般处理方法是:捕获InterruptedException
异常,然后重新设置中断状态,即调用Thread.currentThread().interrupt();
我们来看示例代码:
public class Main extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 模拟任务代码
Thread.sleep(2000);
} catch (InterruptedException e) {
// ... 清理操作
System.out.println(isInterrupted());//false
// 重设中断标志位为true
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());//true
}
public static void main(String[] args) {
Main t = new Main();
t.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();
}
}
4. 如何安全的停止线程
前面提到中断状态只是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。 一般的,可以用以下方式来安全的停止线程:
- 中断方式
- volatile修饰的boolean变量方式
代码举例如下:
public class Shutdown {
public static void main(String[] args) throws Exception {
Runner one = new Runner();
Thread t = new Thread(one, "t");
t.start();
// 睡眠1秒,让Runner线程充分运行,随后能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
t.interrupt();
Runner two = new Runner();
t = new Thread(two, "t");
t.start();
// 睡眠1秒,让Runner线程充分运行,随后能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
}
5. 参考资料
《Java并发编程的艺术》,可联系作者无套路下载可复制的电子版pdf书籍文章来源:https://www.toymoban.com/news/detail-643031.html
官方Thread类的源码和注释文章来源地址https://www.toymoban.com/news/detail-643031.html
到了这里,关于【Java并发编程】线程中断机制(辅以常见案例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!