如何優雅的「打斷」你的線程?
最近看了點 psi-Probe的源代碼,在線程列表頁面,可以對頁面中各個進行線程管理,其中有這樣一個操作,見最左側藍色方框:
點擊每個線程對應的箭頭按鈕,會彈出下方的提示:
實際這上按鈕的操作,是要 「Kill」這個指定的線程。
順著鏈接,我們能看到,具體的實現是這個樣子:
String threadName = ServletRequestUtils.getStringParameter(request,"thread",null);
Thread thread =null;
if(threadName !=null) {
thread = Utils.getThreadByName(threadName);
}
if(thread !=null) {
thread.stop();
}
正如前面的彈窗提示,這裡果然調用的是個危險操作:
Thread.stop()
這裡的 「stop」方法,和「resume」方法、「suspend」方法並稱 Thread 三少,因為線程安全問題,都已經被 @Deprecated了。
官方文檔說的好:
Stopping a thread causes it to unlock all the monitors that it has locked
當我們停止一個線程時,它會悄悄的把所持有的 monitor 鎖釋放了,此時,其他依賴鎖的線程可能就會搶到鎖執行。關鍵此時,當前 stop 的線程實際並沒有處理完所有先決條件,可能這個時候就產生了詭異的問題,加班的日子可能就悄悄來了。
那你說 「Stop 不讓用了,總得讓我們有辦法處理線程吧,哪怕通知他,打斷他一下,讓他停止」。
目前有以下幾種方式來實現。
異常
這點 Thread 也想到了,提供了一個「異常」來達到這個打斷的目的。這個異常在其他線程要打斷某個特定線程時執行,如果是符合條件,會拋出來。此時這個特定線程自行根據這次打斷來判斷後續是不是要再執行線程內的邏輯,還是直接跳出處理。
這個異常就是 InterruptedException。一般使用方式類似這樣
try{
Thread.sleep(backgroundProcessorDelay*1000L);
}catch(InterruptedException e) {
// 具體在中斷通知後的操作
}
xxxThread.interrupt();
目前有以下方法能夠進行這種操作
Thread.sleep
Thread.join
Object.wait
以wait方法為例,我們來看文檔里的描述
*@throwsInterruptedException if any thread interrupted the
* current thread before or while thecurrent thread
* was waiting for a notification. Theinterrupted
* statusof the current thread is cleared when
* this exception is thrown.
這裡有一點信息: 「interrupted status」,這個是個狀態標識,在Thread類中,可以通過 isInterrupted來判斷當前線程是否被中斷。這個標識也可以用來作為一個退出線程執行的標識來直接使用。 但例外是阻塞方法在收到中斷方法調用後,這個標識會被清除重置,所以需要注意下。
我們在執行阻塞方法線程的interrupt方法時,此時並不能拿到這個標識。
另外,拿到異常時,需要關注,如果是類似於後台循環執行的調度線程,在收到中斷異常時需要處理異常再 break 才能跳出,否則只是相當於一個空操作。
目前一些程序里用這種的倒不多,用下面這種的多一些。
退出標識
對於一些長駐線程,會在某些時候需要退出執行,這種情況下,常採用的操作類似這樣, 以Tomcat 的NioConnector 里的Acceptor為例:
protected classAcceptorextendsAbstractEndpoint.Acceptor {
@Override
public voidrun() {
interrorDelay =;
// Loop until we receive a shutdown command
while(running) { //標識1
// Loop if endpoint is paused
while(paused&&running) { //標識2
state= AcceptorState.PAUSED;
try{
Thread.sleep(50);
}catch(InterruptedException e) {
// Ignore
}
}
if(!running) {
break;
}
...
}
用這種退出標識時,記得一定要聲明為 volatile ,類似這樣:
/**
* Running state of the endpoint.
*/
protected volatile booleanrunning=false;
/**
* Will be set to true whenever the endpoint is paused.
*/
protected volatile booleanpaused=false;
否則因為多線程的可見性問題, 這個線程可能一直都不會退出。
總體來說,如果處理sleep/wait等操作,擔心時間太長,可以通過 interrupt 來進行,對於駐留線程,可以通過退出標識來處理。
TAG:Tomcat那些事兒 |