線程的定義給咱們提供了併發執行多個任務的方式,大多數狀況下咱們會讓每一個任務都自行執行結束,這樣能保證事務的一致性,可是有時咱們但願在任務執行中取消任務,使線程中止。在java中要讓線程安全、快速、可靠地停下來並非一件容易的事,java也沒有提供任何可靠的方法終止線程的執行。javascript
線程調度策略中有搶佔式和協做式兩個概念,與之相似的是中斷機制也有協做式和搶佔式。html
歷史上Java曾經使用stop()方法終止線程的運行,他們屬於搶佔式中斷。但它引來了不少問題,早已被JDK棄用。調用stop()方法則意味着:java
①將釋放該線程所持的全部鎖,並且鎖的釋放不可控。
②即刻將拋出ThreadDeath異常,無論程序運行到哪裏,但它不老是有效,若是存在被終止線程的鎖競爭;node
第一點將致使數據一致性問題,這個很好理解,通常數據加鎖就是爲了保護數據的一致性,而線程中止伴隨所持鎖的釋放,極可能致使被保護的數據呈現不一致性,最終致使程序運算出現錯誤。安全
第二點比較模糊,它要說明的問題就是可能存在某種狀況stop()方法不能及時終止線程,甚至可能終止不了線程。看以下代碼會發生什麼狀況,看起來線程mt由於執行了stop()方法將中止,按理來講就算execut方法是一個死循環,只要執行了stop()方法線程將結束,無限循環也將結束。其實不會,由於咱們在execute方法使用了synchronized修飾,同步方法表示在執行execute時將對mt對象進行加鎖,另外,Thread的stop()方法也是同步的,因而在調用mt線程的stop()方法前必須獲取mt對象鎖,但mt對象鎖被execute方法佔用,且不釋放,因而stop()方法永遠獲取不了mt對象鎖,最後獲得一個結論,使用stop()方法中止線程不可靠,它未必總能有效終止線程。併發
public class ThreadStop {
public static void main(String[] args) {
Thread mt= new MyThread();
mt.start();
try {
Thread.currentThread().sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
mt.stop();
}
static class MyThread extends Thread {
public void run() {
execute();
}
private synchronized void execute() {
while(true) {
}
}
}
}複製代碼
經歷了很長時間的發展,Java最終選擇用一種協做式的中斷機制實現中斷。協做式中斷的原理很簡單,其核心是先對中斷標識進行標記,某線程設置某線程的中斷標識位,被標記了中斷位的線程在適當的時間節點會拋出異常,捕獲異常後作相應的處理。實現協做中斷有三個要點須要考慮:框架
①是在Java層面實現輪詢中斷標識仍是在JVM中實現;
②輪詢的顆粒度的控制,通常顆粒度要儘可能小週期儘可能短以保證響應的及時性;
③輪詢的時間節點的選擇,其實就是在哪些方法裏面輪詢,例如JVM將Thread類的wait()、sleep()、join()等方法都實現中斷標識的輪詢操做。工具
中斷標識放在哪裏?中斷是針對線程實例而言,從Java層面上看,標識變量放到線程中確定再合適不過了,但因爲由JVM維護,因此中斷標識具體由本地方法維護。在Java層面僅僅留下幾個API用於操做中斷標識,以下,優化
public class Thread{
public void interrupt() {……}
public Boolean isInterrupted() {……}
public static Booleaninterrupted() {……}
}複製代碼
上面三個方法依次用於設置線程爲中斷狀態、判斷線程狀態是否中斷、清除當前線程中斷狀態並返回它以前的值。經過interrupt()方法設置中斷標識,假如在非阻塞線程則僅僅只是改變了中斷狀態,線程將繼續往下運行,但假如在可取消阻塞線程中,如正在執行sleep()、wait()、join()等方法的線程則會由於被設置了中斷狀態而拋出InterruptedException異常,程序對此異常捕獲處理。ui
上面提到的三個要點:
針對三要點來看看java併發框架中是如何支持中斷的,主要在等待獲取鎖的過程當中提供中斷操做,下面是僞代碼。只需增長加紅加粗部分邏輯便可實現中斷支持,在循環體中每次循環都對當前線程中斷標識位進行判斷,一旦檢查到線程被標記爲中斷則拋出InterruptedException異常,高層代碼對此異常捕獲處理即完成中斷處理。總結起來就是java併發工具獲取鎖的中斷機制是在Java層面實現的,輪詢時間節點選擇在不斷作嘗試獲取鎖操做過程當中,每一個循環的顆粒度比較小,響應速度得以保證,且循環過程不存在阻塞風險,保證中斷檢測不會失效。
if(嘗試獲取鎖失敗) {
建立node
使用CAS方式把node插入到隊列尾部
while(true){
if(嘗試獲取鎖成功而且 node的前驅節點爲頭節點){
把當前節點設置爲頭節點
跳出循環
}else{
使用CAS方式修改node前驅節點的waitStatus標識爲signal
if(修改爲功){
掛起當前線程
if(當前線程中斷位標識爲true)
拋出InterruptedException異常
}
}
}
}複製代碼
判斷線程是否處於中斷狀態其實很簡單,只需使用Thread.interrupted()操做,若是爲true則說明線程處於中斷位,並清除中斷位。至此java併發工具實現了支持中斷的獲取鎖操做。
本文從java發展過程分析了搶佔式中斷及協做式中斷,因爲搶佔式存在一些缺陷如今已不推薦使用,而協做式中斷做爲推薦作法,儘管在響應時間較長,但其具備無可比擬的優點。
協做式中斷咱們能夠在JVM層面實現,一樣也能夠在Java層面實現,例如JDK併發工具的中斷便是在Java層面實現,不過若是繼續深究是由於Java留了幾個API供咱們操做線程的中斷標識位,這才使Java層面實現中斷操做得以實現。
對於java的協做式中斷機制有人確定有人批評,批評者說java沒有搶佔式中斷機制,且協做式中斷機制迫使開發者必須維護中斷狀態,迫使開發者必須處理InterruptedException。但確定者則認爲,雖然協做式中斷機制推遲了中斷請求的處理,但它爲開發人員提供更靈活的中斷處理策略,響應性可能不及搶佔式,但程序健壯性更強。
====廣告時間,可直接跳過====
鄙人的新書《Tomcat內核設計剖析》已經在京東預售了,有須要的朋友能夠到 item.jd.com/12185360.ht… 進行預約。感謝各位朋友。
=========================
相關閱讀:
從JDK源碼角度看併發鎖的優化
從JDK源碼角度看線程的阻塞和喚醒
從JDK源碼角度看併發競爭的超時
從JDK源碼角度看Java併發的公平性
歡迎關注: