| 時(shí)間:2007-02-08 作者:axman 瀏覽次數: 379 本文關(guān)鍵字:Java, 多線(xiàn)程, 線(xiàn)程, 線(xiàn)程對象, 單線(xiàn)程, go deep into java |
|

不客氣地說(shuō),至少有一半人認為,線(xiàn)程的“中斷”就是讓線(xiàn)程停止。如果你也這么認為,那你對多線(xiàn)程編程還沒(méi)有入門(mén)。
在java中,線(xiàn)程的中斷(interrupt)只是改變了線(xiàn)程的中斷狀態(tài),至于這個(gè)中斷狀態(tài)改變后帶來(lái)的結果,那是無(wú)法確定的,有時(shí)它更是讓停止中的線(xiàn)程繼續執行的唯一手段。不但不是讓線(xiàn)程停止運行,反而是繼續執行線(xiàn)程的手段。
對于執行一般邏輯的線(xiàn)程,如果調用它的interrupt()方法,那么對這個(gè)線(xiàn)程沒(méi)有任何影響,比如線(xiàn)程a正在執行:while(條件) x ++;這樣的語(yǔ)句,如果其它線(xiàn)程調用a.interrupt();那么并不會(huì )影響a對象上運行的線(xiàn)程,如果在其它線(xiàn)程里測試a的中斷狀態(tài)它已經(jīng)改變,但并不會(huì )停止這個(gè)線(xiàn)程的運行。在一個(gè)線(xiàn)程對象上調用interrupt()方法,真正有影響的是wait,join,sleep方法,當然這三個(gè)方法包括它們的重載方法。
請注意:[上面這三個(gè)方法都會(huì )拋出InterruptedException],記住這句話(huà),下面我會(huì )重復。一個(gè)線(xiàn)程在調用interrupt()后,自己不會(huì )拋出InterruptedException異常,所以你看到interrupt()并沒(méi)有拋出這個(gè)異常,所以我上面說(shuō)如果線(xiàn)程a正在執行while(條件) x ++;你調用a.interrupt();后線(xiàn)程會(huì )繼續正常地執行下去。
但是,如果一個(gè)線(xiàn)程被調用了interrupt()后,它的狀態(tài)是已中斷的。這個(gè)狀態(tài)對于正在執行wait,join,sleep的線(xiàn)程,卻改變了線(xiàn)程的運行結果。
一、對于wait中等待notify/notifyAll喚醒的線(xiàn)程,其實(shí)這個(gè)線(xiàn)程已經(jīng)“暫停”執行,因為它正在某一對象的休息室中,這時(shí)如果它的中斷狀態(tài)被改變,那么它就會(huì )拋出異常。這個(gè)InterruptedException異常不是線(xiàn)程拋出的,而是wait方法,也就是對象的wait方法內部會(huì )不斷檢查在此對象上休息的線(xiàn)程的狀態(tài),如果發(fā)現哪個(gè)線(xiàn)程的狀態(tài)被置為已中斷,則會(huì )拋出InterruptedException,意思就是這個(gè)線(xiàn)程不能再等待了,其意義就等同于喚醒它了。
這里唯一的區別是,被notify/All喚醒的線(xiàn)程會(huì )繼續執行wait下面的語(yǔ)句,而在wait中被中斷的線(xiàn)程則將控制權交給了catch語(yǔ)句。一些正常的邏輯要被放到catch中來(lái)運行。但有時(shí)這是唯一手段,比如一個(gè)線(xiàn)程a在某一對象b的wait中等待喚醒,其它線(xiàn)程必須獲取到對象b的監視鎖才能調用b.notify()[All],否則你就無(wú)法喚醒線(xiàn)程a,但在任何線(xiàn)程中可以無(wú)條件地調用a.interrupt();來(lái)達到這個(gè)目的。只是喚醒后的邏輯你要放在catch中,當然同notify/All一樣,繼續執行a線(xiàn)程的條件還是要等拿到b對象的監視鎖。
二、對于sleep中的線(xiàn)程,如果你調用了Thread.sleep(一年);現在你后悔了,想讓它早些醒過(guò)來(lái),調用interrupt()方法就是唯一手段,只有改變它的中斷狀態(tài),讓它從sleep中將控制權轉到處理異常的catch語(yǔ)句中,然后再由catch中的處理轉換到正常的邏輯。同樣地,于join中的線(xiàn)程你也可以這樣處理。
對于一般介紹多線(xiàn)程模式的書(shū)上,他們會(huì )這樣來(lái)介紹:當一個(gè)線(xiàn)程被中斷后,在進(jìn)入wait,sleep,join方法時(shí)會(huì )拋出異常。是的,這一點(diǎn)也沒(méi)有錯,但是這有什么意義呢?如果你知道那個(gè)線(xiàn)程的狀態(tài)已經(jīng)處于中斷狀態(tài),為什么還要讓它進(jìn)入這三個(gè)方法呢?當然有時(shí)是必須這么做的,但大多數時(shí)候沒(méi)有這么做的理由,所以我上面主要介紹了在已經(jīng)調用這三個(gè)方法的線(xiàn)程上調用interrupt()方法讓它從"暫停"狀態(tài)中恢復過(guò)來(lái)。這個(gè)恢復過(guò)來(lái)就可以包含兩個(gè)目的:
一、[可以使線(xiàn)程繼續執行],那就是在catch語(yǔ)句中招待醒來(lái)后的邏輯,或由catch語(yǔ)句轉回正常的邏輯??傊菑膚ait,sleep,join的暫停狀態(tài)活過(guò)來(lái)了。
二、[可以直接停止線(xiàn)程的運行],當然在catch中什么也不處理,或return,那么就完成了當前線(xiàn)程的使命,可以使在上面“暫停”的狀態(tài)中立即真正的“停止”。
中斷線(xiàn)程
有了上一節[線(xiàn)程的中斷],我們就好進(jìn)行如何[中斷線(xiàn)程]了。這絕對不是玩一個(gè)文字游戲。是因為“線(xiàn)程的中斷”并不能保證“中斷線(xiàn)程”,所以我要特別地分為兩節來(lái)說(shuō)明。這里說(shuō)的“中斷線(xiàn)程”意思是“停止線(xiàn)程”,而為什么不用“停止線(xiàn)程”這個(gè)說(shuō)法呢?因為線(xiàn)程有一個(gè)明確的stop方法,但它是反對使用的,所以請大家記住,在java中以后不要提停止線(xiàn)程這個(gè)說(shuō)法,忘記它!但是,作為介紹線(xiàn)程知識的我,我仍然要告訴你為什么不用“停止線(xiàn)程”的理由。
[停止線(xiàn)程]
當在一個(gè)線(xiàn)程對象上調用stop()方法時(shí),這個(gè)線(xiàn)程對象所運行的線(xiàn)程就會(huì )立即停止,并拋出特殊的ThreadDeath()異常。這里的“立即”因為太“立即”了,就象一個(gè)正在擺弄自己的玩具的孩子,聽(tīng)到大人說(shuō)快去睡覺(jué)去,就放著(zhù)滿(mǎn)地的玩具立即睡覺(jué)去了。這樣的孩子是不乖的。
假如一個(gè)線(xiàn)程正在執行:
synchronized void {x = 3;y = 4;}由于方法是同步的,多個(gè)線(xiàn)程訪(fǎng)問(wèn)時(shí)總能保證x,y被同時(shí)賦值,而如果一個(gè)線(xiàn)程正在執行到x = 3;時(shí),被調用了stop()方法,即使在同步塊中,它也干脆地stop了,這樣就產(chǎn)生了不完整的殘廢數據。而多線(xiàn)程編程中最最基礎的條件要保證數據的完整性,所以請忘記線(xiàn)程的stop方法,以后我們再也不要說(shuō)“停止線(xiàn)程”了。
如何才能“結束”一個(gè)線(xiàn)程?
[中斷線(xiàn)程]
結束一個(gè)線(xiàn)程,我們要分析線(xiàn)程的運行情況。也就是線(xiàn)程正在干什么。如果那個(gè)孩子什么事也沒(méi)干,那就讓他立即去睡覺(jué)。而如果那個(gè)孩子正在擺弄他的玩具,我們就要讓它把玩具收拾好再睡覺(jué)。
所以一個(gè)線(xiàn)程從運行到真正的結束,應該有三個(gè)階段:
在我的JDBC專(zhuān)欄中我N次提醒在一個(gè)SQL邏輯結束后,無(wú)論如何要保證關(guān)閉Connnection那就是在finally從句中進(jìn)行。同樣,線(xiàn)程在結束前的工作應該在finally中來(lái)保證線(xiàn)程退出前一定執行:
try{正在邏輯}catch(){}finally{清理工作}那么如何讓一個(gè)線(xiàn)程結束呢?既然不能調用stop,可用的只的interrupt()方法。但interrupt()方法只是改變了線(xiàn)程的運行狀態(tài),如何讓它退出運行?對于一般邏輯,只要線(xiàn)程狀態(tài)已經(jīng)中斷,我們就可以讓它退出,所以這樣的語(yǔ)句可以保證線(xiàn)程在中斷后就能結束運行:
while(!isInterrupted()){正常邏輯}這樣如果這個(gè)線(xiàn)程被調用interrupt()方法,isInterrupted()為true,就會(huì )退出運行。但是如果線(xiàn)程正在執行wait,sleep,join方法,你調用interrupt()方法,這個(gè)邏輯就不完全了。
如果一個(gè)有經(jīng)驗的程序員來(lái)處理線(xiàn)程的運行的結束:
public void run(){try{while(!isInterrupted()){正常工作}}catch(Exception e){return;}finally{清理工作}}我們看到,如果線(xiàn)程執行一般邏輯在調用innterrupt后,isInterrupted()為true,退出循環(huán)后執行清理工作后結束,即使線(xiàn)程正在wait,sleep,join,也會(huì )拋出異常執行清理工作后退出。
這看起來(lái)非常好,線(xiàn)程完全按最我們設定的思路在工作。但是,并不是每個(gè)程序員都有這種認識,如果他聰明的自己處理異常會(huì )如何?事實(shí)上很多或大多數程序員會(huì )這樣處理:
public void run(){while(!isInterrupted()){try{正常工作}catch(Exception e){//nothing}finally{}}}}想一想,如果一個(gè)正在sleep的線(xiàn)程,在調用interrupt后,會(huì )如何?wait方法檢查到isInterrupted()為true,拋出異常,而你又沒(méi)有處理。而一個(gè)拋出了InterruptedException的線(xiàn)程的狀態(tài)馬上就會(huì )被置為非中斷狀態(tài),如果catch語(yǔ)句沒(méi)有處理異常,則下一次循環(huán)中isInterrupted()為false,線(xiàn)程會(huì )繼續執行,可能你N次拋出異常,也無(wú)法讓線(xiàn)程停止。
那么如何能確保線(xiàn)程真正停止?在線(xiàn)程同步的時(shí)候我們有一個(gè)叫“二次惰性檢測”(doublecheck),能在提高效率的基礎上又確保線(xiàn)程真正中同步控制中。那么我把線(xiàn)程正確退出的方法稱(chēng)為“雙重安全退出”,即不以isInterrupted()為循環(huán)條件。而以一個(gè)標記作為循環(huán)條件:
class MyThread extend Thread{private boolean isInterrupted = false;//這一句以后要修改public void interrupt(){isInterrupted = true;super.interrupt();}public void run(){while(!isInterrupted){try{正常工作}catch(Exception e){//nothing}finally{}}}}試試這段程序,可以正確工作嗎?
對于這段程序仍然還有很多可說(shuō)的地方,先到這里吧。
聯(lián)系客服