天天看點

從JDK源碼角度看java并發線程的中斷

        線程的定義給我們提供了并發執行多個任務的方式,大多數情況下我們會讓每個任務都自行執行結束,這樣能保證事務的一緻性,但是有時我們希望在任務執行中取消任務,使線程停止。在java中要讓線程安全、快速、可靠地停下來并不是一件容易的事,java也沒有提供任何可靠的方法終止線程的執行。

        線程排程政策中有搶占式和協作式兩個概念,與之類似的是中斷機制也有協作式和搶占式。

        曆史上Java曾經使用stop()方法終止線程的運作,他們屬于搶占式中斷。但它引來了很多問題,早已被JDK棄用。調用stop()方法則意味着

        ①将釋放該線程所持的所有鎖,而且鎖的釋放不可控。

        ②即刻将抛出ThreadDeath異常,不管程式運作到哪裡,但它不總是有效,如果存在被終止線程的鎖競争;

        第一點将導緻資料一緻性問題,這個很好了解,一般資料加鎖就是為了保護資料的一緻性,而線程停止伴随所持鎖的釋放,很可能導緻被保護的資料呈現不一緻性,最終導緻程式運算出現錯誤。第二點比較模糊,它要說明的問題就是可能存在某種情況stop()方法不能及時終止線程,甚至可能終止不了線程。看如下代碼會發生什麼情況,看起來線程mt因為執行了stop()方法将停止,按理來說就算execut方法是一個死循環,隻要執行了stop()方法線程将結束,無限循環也将結束。其實不會,因為我們在execute方法使用了synchronized修飾,同步方法表示在執行execute時将對mt對象進行加鎖,另外,Thread的stop()方法也是同步的,于是在調用mt線程的stop()方法前必須擷取mt對象鎖,但mt對象鎖被execute方法占用,且不釋放,于是stop()方法永遠擷取不了mt對象鎖,最後得到一個結論,使用stop()方法停止線程不可靠,它未必總能有效終止線程。

        經曆了很長時間的發展,Java最終選擇用一種協作式的中斷機制實作中斷。協作式中斷的原理很簡單,其核心是先對中斷辨別進行标記,某線程設定某線程的中斷辨別位,被标記了中斷位的線程在适當的時間節點會抛出異常,捕獲異常後做相應的處理。實作協作中斷有三個要點需要考慮:①是在Java層面實作輪詢中斷辨別還是在JVM中實作;②輪詢的顆粒度的控制,一般顆粒度要盡量小周期盡量短以保證響應的及時性;③輪詢的時間節點的選擇,其實就是在哪些方法裡面輪詢,例如JVM将Thread類的wait()、sleep()、join()等方法都實作中斷辨別的輪詢操作。

        中斷辨別放在哪裡?中斷是針對線程執行個體而言,從Java層面上看,辨別變量放到線程中肯定再合适不過了,但由于由JVM維護,是以中斷辨別具體由本地方法維護。在Java層面僅僅留下幾個API用于操作中斷辨別,如下,

        上面三個方法依次用于設定線程為中斷狀态、判斷線程狀态是否中斷、清除目前線程中斷狀态并傳回它之前的值。通過interrupt()方法設定中斷辨別,假如在非阻塞線程則僅僅隻是改變了中斷狀态,線程将繼續往下運作,但假如在可取消阻塞線程中,如正在執行sleep()、wait()、join()等方法的線程則會因為被設定了中斷狀态而抛出InterruptedException異常,程式對此異常捕獲處理。

        上面提到的三個要點,第一是輪詢在哪個層面實作,這個沒有特别的要求,在實際中隻要不出現邏輯問題,在Java層面或JVM層面實作都是可以的,例如常用的線程睡眠、等待等操作是通過JVM實作,而java并發架構工具裡面的中斷則放到Java實作,不管在哪個層面上去實作,在輪詢過程中都一定要能保證不會産生阻塞。第二是要保證輪詢的顆粒度盡可能的小周期盡可能短,這關系到中斷響應的速度。第三點是關于輪詢的時間節點的選取。

        針對三要點來看看java并發架構中是如何支援中斷的,主要在等待擷取鎖的過程中提供中斷操作,下面是僞代碼。隻需增加加紅加粗部分邏輯即可實作中斷支援,在循環體中每次循環都對目前線程中斷辨別位進行判斷,一旦檢查到線程被标記為中斷則抛出InterruptedException異常,高層代碼對此異常捕獲處理即完成中斷處理。總結起來就是java并發工具擷取鎖的中斷機制是在Java層面實作的,輪詢時間節點選擇在不斷做嘗試擷取鎖操作過程中,每個循環的顆粒度比較小,響應速度得以保證,且循環過程不存在阻塞風險,保證中斷檢測不會失效。

        判斷線程是否處于中斷狀态其實很簡單,隻需使用Thread.interrupted()操作,如果為true則說明線程處于中斷位,并清除中斷位。至此java并發工具實作了支援中斷的擷取鎖操作。

        本文從java發展過程分析了搶占式中斷及協作式中斷,由于搶占式存在一些缺陷現在已不推薦使用,而協作式中斷作為推薦做法,盡管在響應時間較長,但其具有無可比拟的優勢。協作式中斷我們可以在JVM層面實作,同樣也可以在Java層面實作,例如JDK并發工具的中斷即是在Java層面實作,不過如果繼續深究是因為Java留了幾個API供我們操作線程的中斷辨別位,這才使Java層面實作中斷操作得以實作。            對于java的協作式中斷機制有人肯定有人批評,批評者說java沒有搶占式中斷機制,且協作式中斷機制迫使開發者必須維護中斷狀态,迫使開發者必須處理InterruptedException。但肯定者則認為,雖然協作式中斷機制推遲了中斷請求的處理,但它為開發人員提供更靈活的中斷處理政策,響應性可能不及搶占式,但程式健壯性更強。

====廣告時間,可直接跳過====

=========================

歡迎關注: