官术网_书友最值得收藏!

1.8 如何正確終止線程

線程通過start()方法啟動后,會在run()方法執行結束后進入終止狀態。

那么我們如何終止一個正在運行的線程呢?大家應該都知道stop()方法,這個方法是不安全的,因為該方法會導致兩個問題。

? 立即拋出ThreadDeath異常,在run()方法中任何一個執行指令都可能拋出ThreadDeath異常。

? 會釋放當前線程所持有的所有的鎖,這種鎖的釋放是不可控的。

下面來看一段示例代碼:

ThreadStopExample演示了通過stop()方法中斷一個線程造成的問題,該代碼的運行結果如下。

從運行結果可以看出兩個問題:

? 在run()方法中,代碼System.out.println("the code that it will be executed");還未執行,就因為ThreadDeath異常導致線程中斷了,造成業務處理的不完整性。

? 觀察內容Running..17103java.lang.ThreadDeath,我們使用的是println()方法,但是這里并沒有換行,為什么呢?我們來看一下println()方法的代碼。

println()方法包含兩個操作,一個操作是輸出print(x),另一個操作是換行,為了保證兩個操作的原子性,增加了synchronized同步鎖,理論上來說不應該出現問題。但是stop()方法會釋放synchronized同步鎖,使得這兩個操作不是原子的,從而導致newLine()方法還沒執行,線程就被中斷了。

因此,在實際應用中,一定不能使用stop()方法來中斷線程,那么如何安全地實現線程的中斷呢?

1.8.1 關于安全中斷線程的思考

在Thread中提供了一個interrupt()方法,從名字上來看表示中斷線程,但是實際上它并沒有像stop()方法那樣提供可以直接中斷線程的功能,而是基于一個信號量來進行線程中斷的通知。在了解interrupt()方法之前,不妨來思考一下,如果我們想讓一個線程安全中斷,應該怎么做?

實際上,在線程異步運行過程中,該線程的執行情況只有自己知道,如果想要中斷一個正在運行的線程,很顯然不能直接從外部強制中斷,只能由運行的線程自己來決定,這樣才能保證中斷過程的安全性。為了達到這個目的,我們需要做兩件事情。

? 外部線程需要發送一個中斷信號給正在運行的線程。

? 正在運行的線程需要根據這個信號來判斷是否終止線程。

如圖1-7所示,這是對線程安全中斷模型的猜想,InterruptThread線程向正在運行的線程RunningThread發送一個中斷信號,RunningThread線程收到該信號之后,在run()方法中提供一個信號判斷的邏輯,從而達到線程中斷的目的。簡單來說,只有把線程中斷全力交給正在運行的線程,才能真正意義上達到安全中斷的目的。

圖1-7 線程安全中斷模型猜想

1.8.2 安全中斷線程之interrupt

在Thread中提供了一個interrupt()方法,用來向指定線程發送中斷信號,收到該信號的線程可以使用isInterrupted()方法來判斷是否被中斷,具體代碼如下。

在上述代碼中,創建了一個線程InterruptExample,該線程使用while()循環不斷進行空轉。while()循環的判斷條件是Thread.currentThread().isInterrupted(),它表示當前線程中斷的標記狀態,默認是false,一旦其他線程通過interrupt()方法對該線程進行中斷,那么循環判斷條件就會變成true,這會導致while()循環條件被破壞,從而使線程執行結束。

上述代碼的輸出結果如下。

從這個實例中可以發現,interrupt()方法并沒有武斷地把運行中的線程停止,而是通過傳遞標識的方式讓運行的線程自己決定是否停止。這意味著該線程在收到該信號后,可以繼續把run()方法中的指令運行完成,最后讓run()方法安全執行結束,完成線程的中斷功能。

1.8.3 如何中斷處于阻塞狀態下的線程

假設一個線程處于阻塞狀態,通過interrupt()方法可以中斷嗎?答案是可以的,那么怎么做呢?

如果線程因為sleep()、Object.wait()等方法阻塞,而其他線程想通過interrupt()方法對該線程進行中斷,那么這個線程必須先被喚醒,否則無法響應中斷信號。

在BlockedThreadInterruptExample這個案例中,演示了一個被sleep()方法阻塞的線程的中斷過程,代碼如下。

上述代碼運行后,會輸出如下結果,但是線程并沒有結束,因為“線程被中斷”這句話沒有被打印出來。

也就是說,當調用interrupt()方法中斷BlockedThreadInterruptExample線程時,該線程拋出了InterruptedException異常,說明interrupt()方法會先喚醒被阻塞的線程。

但是從運行結果來看,中斷前后的標識都是false,這是否意味著interrupt()方法無法處理這種情況呢?其實不是,這里仍然涉及中斷權問題,當被阻塞的線程被其他線程使用interrupt()方法喚醒時,在拋出InterruptedException異常之前,會先把線程中斷狀態進行復位,也就是將中斷標記變成false。

注意,除InterruptedException被動觸發線程復位外,還有一個Thread.interrupted()方法可以主動觸發線程中斷標識的復位。

這樣設計的目的,仍然是把線程中斷的選擇權交給正在運行的線程,我們可以在捕獲的異常中實現一些后置操作,最終決定是否要中斷該線程。

因此,我們繼續來看BlockedThreadInterruptExample線程,如果在線程拋出InterruptedException異常后,仍然要堅持中斷,則再次調用Thread.currentThread().interrupt();即可,具體代碼如下。

通過上述代碼演示,我們發現對于涉及線程阻塞的方法如Thread.join()、Object.wait()、Thread.sleep()等,都會拋出InterruptedException異常,之所以拋出這個異常,是因為如果需要讓一個處于阻塞狀態下的線程被中斷,那么該線程必然需要先被喚醒并響應中斷請求,而InterruptedException就是一種響應方式。

一旦開發者捕獲這個異常,就說明當前線程收到了中斷請求,我們可以在這個中斷異常中,根據實際業務情況進行相應的資源回收及后置處理。需要注意的是,InterruptedException在拋出之前會先對線程中斷標識進行復位,目的是讓運行的線程自己來決定何時中斷。

因此,InterruptedException異常的拋出并不意味著線程必須終止,而是提醒當前線程有中斷的操作發生,至于接下來怎么處理取決于線程本身,比如:

? 直接捕獲異常不做任何處理。

? 將異常往外拋出。

? 停止當前線程,并打印異常信息。

1.8.4 interrupt()方法的實現原理

在interrupt()方法觸發中斷之后,從被中斷的線程使用Thread.currentThread().isInterrupted()方法來判斷中斷狀態來看,似乎是基于共享一個boolean變量來實現通信的,但是我們通過isInterrupted()方法的源碼發現,isInterrupted()方法調用了一個native()方法,返回一個boolean值,代碼如下:

native()是一個本地方法,它是非Java語言實現的一個接口,使用C/C++語言在其他文件中定義實現。簡單地說,native()方法就是在Java中聲明的可以調用非Java語言的方法。

下面我們來看interrupt()方法,發現該方法中也調用了一個interrupt()的本地方法,由此我們不難想象到,線程中斷的信號標識是在JVM中實現的,具體代碼如下:

于是,筆者下載了hotspot的源碼,在Thread.cpp文件中找到interrupt()方法的實現,代碼如下。

在Thread::interrupt()方法中最終調用了os::interrupt()方法,這個方法的實現在os_*.cpp文件中,其中“*”代表不同的操作系統,如圖1-8所示。由于JVM是跨平臺的,所以對于不同的操作系統,線程的調度方式是不一樣的。

圖1-8 os_*.cpp文件

我們以os_linux.cpp文件為例,找到os::interrupt()方法的定義,代碼如下:

上述代碼是用C++寫的,有些地方讀者不一定能看明白,我們主要關注以下兩個部分。

? osthread->set_interrupted(true),設置中斷標識為true。

? ((JavaThread*)thread)->parker()->unpark();,unpark()方法用來喚醒線程。

這兩個部分正好印證了前面我們使用interrupt()方法實現的效果,其中set_interrupted()方法是在OSThread中定義的,于是我們定位到osThread.hpp文件,找到該方法的定義代碼如下:

終于,我們在JVM的源碼中看到了_interrupted中斷標記,它是使用volatile修飾的int類型的變量,該變量有兩個值:1和0,其中1代表true,0代表false。另外,這里還提供了一個interrupted()方法,返回一個中斷標識的結果。

如圖1-9所示,該圖表示interrupt()方法的實現原理,當Thread A調用interrupt()方法時,會調用一個native()方法修改JVM中定義的一個interrupted變量,Thread B通過isInterrupted()方法來獲得這個變量的值,進而判斷當前的中斷狀態。注意,interrupted字段使用了volatile修飾,表示它提供了可見性保障。

圖1-9 interrupt()方法的實現原理

主站蜘蛛池模板: 高碑店市| 深州市| 蓬安县| 海淀区| 社旗县| 河北省| 南充市| 深水埗区| 泰和县| 读书| 灵宝市| 银川市| 缙云县| 台州市| 留坝县| 侯马市| 蛟河市| 祁门县| 微山县| 平度市| 诸城市| 广安市| 荥经县| 邛崃市| 中方县| 武鸣县| 合山市| 巴东县| 牡丹江市| 鄂伦春自治旗| 大关县| 枣阳市| 冀州市| 英德市| 扶余县| 彭阳县| 淮阳县| 佛坪县| 多伦县| 长宁县| 晋州市|