java線程的中斷的方式原理分析

雖然本篇是講解線程的中斷方式的,但是我還是想多講解一個知識點,那就是線程的啓動。只有啓動纔有中斷的,我覺得這個線程的啓動很有必要講解的。

線程的啓動

我們知道啓動線程,也就是調用 start()方法去啓動一個線程,當 run 方法中的代碼執行完畢 以後,線程的生命週期也將終止。調用 start 方法的語義是 當前線程告訴 JVM,啓動調用 start 方法的線程。但是它的原理我們知道嗎?我們就來粗略的看一下吧。

線程的啓動原理

很多同學最早學習線程的時候會比較疑惑,啓動一個線程 爲什麼是調用 start 方法,而不是 run 方法,這做一個簡單 的分析,先簡單看一下 start 方法的定義:

我們看到調用 start 方法實際上是調用一個 native 方法 start0()來啓動一個線程,首先 start0()這個方法是在 Thread 的靜態塊中來註冊的,代碼如下:

registerNatives 的本地方法的定義在文件 Thread.c,Thread.c 定義了各個操作系統平臺要用的關於線 程的公共數據和操作,以下是 Thread.c 的全部內容 http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/00cd9dc3c 2b5/src/share/native/java/lang/Thread.c

從 這 段 代 碼 可 以 看 出 , start0() , 實 際 會 執 行 JVM_StartThread方法,這個方法是幹嘛的呢? 從名字上 來看,似乎是在 JVM 層面去啓動一個線程,如果真的是這 樣,那麼在 JVM 層面,一定會調用 Java 中定義的 run 方法。那接下來繼續去找找答案。我們找到 jvm.cpp 這個文 件;這個文件需要下載 hotspot 的源碼才能找到.

JVM_ENTRY 是用來定義 JVM_StartThread 函數的,在這 個函數裏面創建了一個真正和平臺有關的本地線程. 本着 打破砂鍋查到底的原則,繼續看看 newJavaThread 做了什 麼事情,繼續尋找 JavaThread 的定義

在 hotspot 的源碼中 thread.cpp 文件中 1558 行的位置可 以找到

這個方法有兩個參數,第一個是函數名稱,線程創建成功 之後會根據這個函數名稱調用對應的函數;第二個是當前 進程內已經有的線程數量。最後我們重點關注與一下 os::create_thread,實際就是調用平臺創建線程的方法來創 建線程。

接下來就是線程的啓動,會調用 Thread.cpp 文件中的 Thread::start(Thread* thread)方法,代碼如下

start 方法中有一個函數調用: os::start_thread(thread);, 調用平臺啓動線程的方法,最終會調用 Thread.cpp 文件中 的 JavaThread::run()方法

線程的終止

線程的啓動過程大家都非常熟悉,但是如何終止一個線程 呢? 這是面試過程中針對 3 年左右的人喜歡問到的一個 題目。
線程的終止,並不是簡單的調用 stop 命令去。雖然 api 仍 然可以調用,但是和其他的線程控制方法如 suspend、 resume 一樣都是過期了的不建議使用,就拿 stop 來說, stop 方法在結束一個線程時並不會保證線程的資源正常釋 放,因此會導致程序可能出現一些不確定的狀態。 要優雅的去中斷一個線程,在線程中提供了一個 interrupt 方法

interrupt 方法

當其他線程通過調用當前線程的 interrupt 方法,表示向當 前線程打個招呼,告訴他可以中斷線程的執行了,至於什麼時候中斷,取決於當前線程自己。 線程通過檢查資深是否被中斷來進行相應,可以通過 isInterrupted()來判斷是否被中斷。 通過下面這個例子,來實現了線程終止的邏輯。

我們來看下代碼實現:

加上thread.interrupt()的代碼執行結果:

去掉的結果:

這種通過標識位或者中斷操作的方式能夠使線程在終止時 有機會去清理資源,而不是武斷地將線程停止,因此這種 終止線程的做法顯得更加安全和優雅

Thread.interrupted

上面的案例中,通過 interrupt,設置了一個標識告訴線程 可以終止了,線程中還提供了靜態方法 Thread.interrupted()對設置中斷標識的線程復位。比如在 上面的案例中,外面的線程調用 thread.interrupt 來設置中 斷標識,而在線程裏面,又通過 Thread.interrupted 把線 程的標識又進行了復位

看下運行結果:

不加復位操作:

異常使線程復位:

除了通過 Thread.interrupted 方法對線程中斷標識進行復 位以外,還有一種被動復位的場景,就是對拋出 InterruptedException 異 常 的 方 法 , 在 InterruptedException 拋出之前,JVM 會先把線程的中斷 標識位清除,然後纔會拋出 InterruptedException,這個時 候如果調用 isInterrupted 方法,將會返回 false

這個異常是不影響程序的執行的

爲什麼要復位

Thread.interrupted()是屬於當前線程的,是當前線程對外 界中斷信號的一個響應,表示自己已經得到了中斷信號, 但不會立刻中斷自己,具體什麼時候中斷由自己決定,讓 外界知道在自身中斷前,他的中斷狀態仍然是 false,這就 是復位的原因。

線程的終止原理

我們來看一下 thread.interrupt()方法做了什麼事情:

這個方法裏面,調用了 interrupt0(),這個方法在前面分析 start 方法的時候見過,是一個 native 方法,這裏就不再重 復貼代碼了,同樣,我們找到 jvm.cpp 文件,找到 JVM_Interrupt 的定義

這個方法比較簡單,直接調用了 Thread::interrupt(thr)這 個方法,這個方法的定義在 Thread.cpp 文件中,代碼如下

Thread::interrupt 方法調用了 os::interrupt 方法,這個是調 用平臺的 interrupt 方法,這個方法的實現是在 os_*.cpp 文件中,其中星號代表的是不同平臺,因爲 jvm 是跨平臺 的,所以對於不同的操作平臺,線程的調度方式都是不一 樣的。我們以 os_linux.cpp 文件爲例

set_interrupted(true)實際上就是調用 osThread.hpp 中的 set_interrupted()方法,在 osThread 中定義了一個成員屬 性 volatile jint _interrupted;

通過上面的代碼分析可以知道,thread.interrupt()方法實際 就是設置一個 interrupted 狀態標識爲 true、並且通過 ParkEvent 的 unpark 方法來喚醒線程。

  1. 對於 synchronized 阻塞的線程,被喚醒以後會繼續嘗試

    獲取鎖,如果失敗仍然可能被 park

  2. 在調用 ParkEvent 的 park 方法之前,會先判斷線程的中

    斷狀態,如果爲 true,會清除當前線程的中斷標識

  3. Object.wait 、 Thread.sleep 、 Thread.join 會 拋 出

    InterruptedException

這裏給大家普及一個知識點,爲什麼 Object.wait、 Thread.sleep 和 Thread.join 都 會 拋 出 InterruptedException? 你會發現這幾個方法有一個共同 點,都是屬於阻塞的方法 而阻塞方法的釋放會取決於一些外部的事件,但是阻塞方 法可能因爲等不到外部的觸發事件而導致無法終止,所以 它允許一個線程請求自己來停止它正在做的事情。當一個 方法拋出 InterruptedException 時,它是在告訴調用者如 果執行該方法的線程被中斷,它會嘗試停止正在做的事情 並且通過拋出 InterruptedException 表示提前返回。

所以,這個異常的意思是表示一個阻塞被其他線程中斷了。 然後,由於線程調用了 interrupt()中斷方法,那麼

Object.wait、Thread.sleep 等被阻塞的線程被喚醒以後會 通過 is_interrupted 方法判斷中斷標識的狀態變化,如果發 現中斷標識爲 true,則先清除中斷標識,然後拋出 InterruptedException 需要注意的是,InterruptedException 異常的拋出並不意味 着線程必須終止,而是提醒當前線程有中斷的操作發生, 至於接下來怎麼處理取決於線程本身,比如

1. 直接捕獲異常不做任何處理
2. 將異常往外拋出
3. 停止當前線程,並打印異常信息 爲了讓大家能夠更好的理解上面這段話,我們以 Thread.sleep 爲例直接從 jdk 的源碼中找到中斷標識的清 除以及異常拋出的方法代碼
找到 is_interrupted()方法,linux 平臺中的實現在 os_linux.cpp 文件中,代碼如下

到 Thread.sleep 這個操作在 jdk 中的源碼體現,怎麼找?

相信如果前面大家有認真看的話,應該能很快找到,代碼 在 jvm.cpp 文件中

注意上面加了中文註釋的地方的代碼,先判斷 is_interrupted 的狀態,然後拋出一個 InterruptedException 異常。到此爲止,我們就已經分析清 楚了中斷的整個流程。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章