『中斷技術』實際上是計算機系統中很重要的一個概念,甚至有人說,咱們的操做系統就是「中斷驅動的」。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 認爲,一個還未啓動的線程和一個已經結束的線程,對於他們的中斷是毫無心義的。
總結一下,以上就是咱們『中斷技術』的相關概念,它是一種線程間協做方式,理解它是爲了更優雅的結束線程,使程序走在咱們的預期之中。