『中断技术』实际上是计算机系统中很重要的一个概念,甚至有人说,咱们的操做系统就是「中断驱动的」。java
中断,其实指的就是程序在执行过程当中,发生了某些非正常的事件指示当前进程不能继续执行了,应当获得暂停或终止,而通知正在执行的进程暂停执行的这个操做就叫『中断』。浏览器
中断同时也是咱们实现并发的基础,中断一个线程的执行,调度另外一个线程的执行。bash
若是按照中断事件类型来分,大体上有如下几种类型的中断事件类型:并发
每一种类型的中断事件都对应一位二进制的比特位,系统中也对应一个中断寄存器用于保存当前系统所遇到的全部中断事件,1 表示该类型的中断事件发生,0 表示未发生。ide
中断操做主要分为两种方式,一种叫『抢占式中断』,一种叫『主动式中断』。前者就是在发生中断时,强制剥夺线程的 CPU,后者是在正在执行的线程中断位上标记一下,具体何时中断由线程本身来决定。spa
当线程发现本身有中断事件时,会根据中断事件的类型去对应相应的中断处理程序来处理该中断事件。操作系统
下面咱们看几种类型的中断事件,对应的中断处理程序是如何处理的。线程
一、电源故障(掉电)code
首先,当咱们的系统丢失电源时,系统硬设备是能保证继续工做一小段时间的。这也是为何你的用浏览器浏览这好几个标签,忽然关机了,开机后打开浏览器会提示你上次异常关闭,问你是否恢复的缘由。cdn
而咱们的中断处理程序首先会将当前全部寄存器中的数据经由主存保存到磁盘,接着中止 CPU 的运行,直至停机。
下次开机时,中断处理程序会从磁盘加载中断前的寄存器数据,恢复现场。
二、程序逻辑中断
当咱们的 CPU 执行除运算时遇到除数为零,将产生一个中断事件,对应的处理程序会简单的将错误类型及信息进行一个返回。
内存溢出异常也是同样的处理。
Java API 中线程相关的方法主要有三个:
public void interrupt()
public static boolean interrupted()
public boolean isInterrupted()
interrupt 方法表示中断当前线程,仅仅设置一下线程的中断标记位。interrupted 是一个静态的方法,它将返回当前线程的中断位是否被标记,若是是则返回 true 并清空中断标记位,不然返回 false。
isInterrupted 方法功能是相似于 interrupted 方法的,只不过不管当前线程是否被中断了,都不会清空中断标志位。
咱们看一个例子:
public void test() {
Thread thread = new Thread(){
@Override
public void run(){
for (int i=0; i<50000; i++){
System.out.println("i=" + i);
}
}
};
thread.start();
thread.interrupt();
thread.join();
}
复制代码
这样一段代码,咱们建立一个线程,该线程启动后打印 50000 个数字,可是咱们的主线程中又会去中断该线程。
抢断式中断方式下,thread 线程可能只打印了几个数字,甚至还未开始执行打印操做就被剥夺了 CPU,提早结束生命周期。
而咱们的 Java 中不推荐使用抢断式中断,倡导「一个线程的生命不该该由其余线程终止,应当由它本身选择是否中止」。因此,这段程序会成功打印 50000 个数字,即使 thread 线程的中断标记位已经被标记。
简单修改下,咱们的代码即能响应中断:
每一次打印前都去检查一下本身的中断标记位是否为 true,判断本身是否被中断以采起相应的处理操做。
可是这仅仅是线程处于 RUNNABLE 状态下对于中断请求的响应状况,下面咱们具体看看线程的其余状态下,面对中断请求的响应措施。
RUNNABLE
状态为 RUNNABLE 的线程是拥有 CPU 正在运行的线程,咱们的 interrupt 方法仅仅会设置一下该线程的中断标志位,不会作任何其余的操做,关于你是否响应此中断,由你本身决定。
这一类型的代码,咱们已经在上文介绍了,此处再也不赘述了。
WAITING
WAITING 状态是线程在得到锁的前提下,正常运行过程当中因为缺失一些条件而被迫释放锁,交出 CPU,阻塞到等待队列上,等待别人唤醒的一个状态。
这个状态下的线程一旦被别人 interrupt 中断,将直接抛出异常 java.lang.InterruptedException。咱们看一段代码:
public void test1() {
Object obj = new Object();
Thread thread = new Thread(){
@Override
public void run(){
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
//主线程等待 thread 线程获取 obj 对象锁并阻塞本身到等待队列
Thread.sleep(2000);
thread.interrupt();
}
复制代码
程序直接抛出异常,并清空中断标志位。
你能够思考一下,一个 WAITING 状态的线程被中断为何要抛出一个异常?
其实仍是那个理念,「任何线程都没有权利终止另外一个线程的生命」,一个正在 WAITING 中的线程因为不具备 CPU 的使用权,你中断它,它永远都不会知道本身被中断了直到本身从新竞争到了锁并获得运行。
那么,咱们的主线程在调用 interrupt 方法中断一个线程,当发现它的状态为 WAITING 时,将唤醒它并更改指令寄存器的值以指向异常代码块,期待你本身来处理这个中断。
这也是为何 wait、sleep、join 这些方法必须处理一个受检查的异常 InterruptException 的缘由,由于这些方法会阻塞线程,而若是在阻塞期间收到中断,你也应当提供中断的处理逻辑。
BLOCKED
BLOCKED 状态的线程每每是竞争某个锁失败,而阻塞在某个对象的阻塞队列上的线程。
这个状态的线程和 RUNNABLE 状态的线程同样,对于中断请求不作额外响应,仅仅设置一下中断标志位,具体何时处理中断须要程序本身去循环检测判断。
NEW/TERMINATE
对于这两个状态的线程进行中断请求,目标线程什么也不会作,就连中断标志位也不会被设置,由于 Java 认为,一个还未启动的线程和一个已经结束的线程,对于他们的中断是毫无心义的。
总结一下,以上就是咱们『中断技术』的相关概念,它是一种线程间协做方式,理解它是为了更优雅的结束线程,使程序走在咱们的预期之中。