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