之前講到Thread的創建,那是Thread生命周期的第一步,其後就是通過start()方法來啟動Thread,它會 執行一些內部的管理工作然後調用Thread的run()方法,此時該Thread就是alive(活躍)的,而且我們還可以通 過isAlive()方法來確定該線程是否啟動還是終結。
一旦啟動Thread後,我們就只能執行一個方 法:run(),而run()方法就是負責執行Thread的任務,所以終結Thread的方法很簡單,就是終結run()方法。仔 細查看文檔,我們會發現裡面有一個方法:stop(),似乎可以用來停止Thread,但是這個方法已經被廢除了, 因為它存在著內部的競爭。
我們經常需要一個不斷執行的Thread,然後在某個特定的條件下才會終結 它,方法有很多,但最常用的有設定標記和中斷Thread兩種方式。
我們將之前例子中的Thread改寫一 下:
public class RandomCharacterGenerator extends Thread implements CharacterSource { static char[] chars; static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789"; static { chars = charArray.toCharArray(); } private volatile boolean done = false; Random random; CharacterEventHandler handler; public RandomCharacterGenerator() { random = new Random(); handler = new CharacterEventHandler(); } public int getPauseTime() { return (int) (Math.max(1000, 5000 * random.nextDouble())); } @Override public void addCharacterListener(CharacterListener cl) { handler.addCharacterListener(cl); } @Override public void removeCharacterListener(CharacterListener cl) { handler.removeCharacterListener(cl); } @Override public void nextCharacter() { handler.fireNewCharacter(this, (int) chars[random.nextInt(chars.length)]); } public void run() { while(!done){ nextCharacter(); try { Thread.sleep(getPauseTime()); } catch (InterruptedException ie) { return; } } } public void setDone(){ done = true; } }
現在我們多了一個標記:done,這樣我們就可以在代碼中通過調用setDone()來決定什麼時候停止 該Thread。這裡使用了volatile關鍵字,它主要是為了同步。這點會放在同步這裡講。
設定標記的最大問 題就是我們必須等待標記的狀態,這樣就會造成延遲。當然,這種延遲是無法避免的,但必須想辦法縮短到最 小。於是,中斷Thread這種方法就有它的發揮地方了。
我們可以通過interrupt()方法來中斷Thread, 該方法會造成兩個副作用:
1.它會導致任何的阻塞方法都會拋出InterruptedException,我們必須強 制性的捕獲這個錯誤哪怕我們根本就不需要處理它,這也是java的異常處理機制讓人诟病的一個地方。
2.設定Thread對象內部的標記來指示此Thread已經被中斷了,像是這樣:
public void run (){ while(!isInterrupted()){ ... } }
雖然無法避免延遲,但是延遲已經被縮短了。
無論是采用標記還是中斷的方法,我們之所以無 法消除延遲的原因是我們無法確定是檢查標記先還是調用方法先,這就是所謂的race condition,是線程處理 中永遠無法避免的話題。
Thread不僅可以被終結,還可以暫停,掛起和恢復。
Thread原本有 suspend()方法和resume()方法來執行掛起和恢復,但它們和stop()出於同樣的原因,都被廢除了。
我 們可以通過sleep()方法來掛起Thread,當在指定的時間後,它就會自動恢復。嚴格意義上講,sleep並不等同 於suspend,真正的suspend應該是由一個線程來掛起另一個線程,但是sleep只會影響當前的Thread。要想真 正實現掛起和恢復,我們可以使用等待和通知機制,但這個機制最大的問題就是我們的Thread必須使用該技術 來編寫。
Thread在終結後,如果有可能,我們還需要對它進行善後。即使Thread已經被終結了,但是 其他對象只要還持有它的引用,它們就可以調用該Thread的資源,這也會導致該Thread無法被回收。
但我們有時候還是希望繼續保持該Thread的引用,因為我們想要判別它是否真的已經完成了工作,可以使用 join()方法。join()方法會被阻塞住直到Thread完成它的run()方法,但是這個存在風險:第一次對join()方 法的調用可能會一直被阻塞住很長時間直到Thread真正完成,所以,一般情況下我們還是使用isAlive()方法 來判斷。
由於我們可以通過實現一個Runnable接口來定義我們的任務,所以在判斷所在線程是否已經 中斷的時候,就有一個問題:該任務還沒有綁定到任何線程上。我們可以通過currentThread()方法來獲得當 前Thread的引用,接著調用isInterrupted()來判斷線程是否中斷。