Posted on 2007-07-18 13:04
Matthew Chen 閱讀(233)
評論(0) 編輯 收藏 所屬分類:
Java MultiThread
在一個調試會話期間,使用用戶友好方式從另一個線程區別其中一個線程證明是有幫助的。要區分其中一個線程,Java給一個線程取一個名稱。Thread缺省的名稱是一個短線連字符和一個零開始的數字符號。你可以接受Java的缺省線程名稱或選擇使用你自己的。為了能夠自定義名稱,Thread提供帶有name參數和一個setName(String name)方法的構造器。Thread也提供一個getName()方法返回當前名稱。表2顯示了怎樣通過Thread(String name)創建一個自定義名稱和通過在run()方法中調用getName()檢索當前名稱: |
public static void main (String [] args) |
mt = new MyThread (args [0]); |
class MyThread extends Thread |
super (name); //將名稱傳遞給Thread超類 |
System.out.println ("My name is: " + getName ()); |
你能夠在命令行向MyThread傳遞一個可選的name參數。例如,java NameThatThread X 建立X作為線程的名稱。如果你指定一個名稱失敗,你將看到下面的輸出: |
如果你喜歡,你能夠在MyThread(String name)構造器中將super(name)調用改變成setName(String name)調用——作為setName(name)后一種方法調用達到同樣建立線程名稱的目的——作為super(name)我作為練習保留給你們。 |
Java主要將名稱指派給運行main() 方法的線程,開始線程。你特別要看看當開始線程擲出一個例外對象時在線程“main”的例外顯示的JVM的缺省例外處理打印消息。 |
在這一欄后面,我將向你介紹動畫——在一個表面上重復畫圖形,這稍微不同于完成一個運動畫面。要完成動畫,一個線程必須在它顯示兩個連續畫面時中止。調用Thread的靜態sleep(long millis)方法強迫一個線程中止millis毫秒。另一個線程可能中斷正在休眠的線程。如果這種事發生,正在休眠的線程將醒來并從sleep(long millis)方法擲出一個InterruptedException對象。結果,調用sleep(long millis)的代碼必須在一個try代碼塊中出現——或代碼方法必須在自己的throws子句中包括InterruptedException。 |
為了示范sleep(long millis),我寫了一個CalcPI1應用程序。這個應用程序開始了一個新線程便于用一個數學運算法則計算數學常量pi的值。當新線程計算時,開始線程通過調用sleep(long millis)中止10毫秒。在開始線程醒后,它將打印pi的值,其中新線程存貯在變量pi中。表3給出了CalcPI1的源代碼: |
public static void main (String [] args) |
MyThread mt = new MyThread (); |
Thread.sleep (10); //休眠10毫秒 |
catch (InterruptedException e) |
System.out.println ("pi = " + mt.pi); |
class MyThread extends Thread |
for (int i = 3; i < 100000; i += 2) |
System.out.println ("Finished calculating PI"); |
如果你運行這個程序,你將看到輸出如下(但也可能不一樣): |
為什么輸出不正確呢?畢竟,pi的值應近似等于3.14159。回答是:開始線程醒得太快了。在新線程剛開始計算pi時,開始線程就醒過來讀取pi的當前值并打印其值。我們可以通過將10毫秒延遲增加為更長的值來進行補償。這一更長的值(不幸的是它是依賴于平臺的)將給新線程一個機會在開始線程醒過來之前完成計算。(后面,你將學到一種不依賴平臺的技術,它將防止開始線程醒來直到新線程完成。) |
線程同時提供一個sleep(long millis, int nanos)方法,它將線程休眠millis 毫秒和nanos 納秒。因為多數基于JVM的平臺都不支持納秒級的分解度,JVM 線程處理代碼將納秒數字四舍五入成毫秒數字的近似值。如果一個平臺不支持毫秒級的分解度,JVM 線程處理代碼將毫秒數字四舍五入成平臺支持的最小級分解度的近似倍數。 |
當一個程序調用Thread的start()方法時,在一個新線程調用run()之前有一個時間段(為了初始化)。run()返回后,在JVM清除線程之前有一段時間通過。JVM認為線程立即激活優先于線程調用run(),在線程執行run()期間和run()返回后。在這時間間隔期間,Thread的isAlive()方法返回一個布爾真值。否則,方法返回一個假值。 |
isAlive()在一個線程需要在第一個線程能夠檢查其它線程的結果之前等待另一個線程完成其run()方法的情形下證明是有幫助的。實質上,那些需要等待的線程輸入一個while循環。當isAlive()為其它線程返回真值時,等待線程調用sleep(long millis) (或 sleep(long millis, int nanos))周期性地休眠 (避免浪費更多的CPU循環)。一旦isAlive()返回假值,等待線程便檢查其它線程的結果。 |
你將在哪里使用這樣的技術呢?對于起動器,一個CalcPI1的修改版本怎么樣,在打印pi的值前開始線程在哪里等待新線程的完成?表4的CalcPI2源代碼示范了這一技術: |
public static void main (String [] args) |
MyThread mt = new MyThread (); |
Thread.sleep (10); //休眠10毫秒 |
catch (InterruptedException e) |
System.out.println ("pi = " + mt.pi); |
class MyThread extends Thread |
for (int i = 3; i < 100000; i += 2) |
System.out.println ("Finished calculating PI"); |
CalcPI2的開始線程在10毫秒時間間隔休眠,直到mt.isAlive ()返回假值。當那些發生時,開始線程從它的while循環中退出并打印pi的內容。如果你運行這個程序,你將看到如下的輸出(但不一定一樣): |
一個線程可能對它自己調用isAlive() 方法。然而,這毫無意義,因為isAlive()將一直返回真值。 |
因為while循環/isAlive()方法/sleep()方法技術證明是有用的,Sun將其打包進三個方法組成的一個組合里:join(),join(long millis)和join(long millis, int nanos)。當當前線程想等待其它線程結束時,經由另一個線程的線程對象引用調用join()。相反,當它想其中任意線程等待其它線程結束或等待直到millis毫秒和nanos納秒組合通過時,當前線程調用join(long millis)或join(long millis, int nanos)。(作為sleep()方法,JVM 線程處理代碼將對join(long millis)和join(long millis,int nanos)方法的參數值四舍五入。)表5的CalcPI3源代碼示范了一個對join()的調用: |
public static void main (String [] args) |
MyThread mt = new MyThread (); |
catch (InterruptedException e) |
System.out.println ("pi = " + mt.pi); |
class MyThread extends Thread |
for (int i = 3; i < 100000; i += 2) |
System.out.println ("Finished calculating PI"); |
CalcPI3的開始線程等待與MyThread對象有關被mt引用的線程結束。接著開始線程打印pi的值,其值與CalcPI2的輸出一樣。 |
不要試圖將當前線程與其自身連接,因為這樣當前線程將要永遠等待。 |