《Java程序案例教程》課件第9章_第1頁
《Java程序案例教程》課件第9章_第2頁
《Java程序案例教程》課件第9章_第3頁
《Java程序案例教程》課件第9章_第4頁
《Java程序案例教程》課件第9章_第5頁
已閱讀5頁,還剩75頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第9章多線程9.1進程與線程

9.2認識線程

9.3線程的狀態(tài)

9.4線程操作的一些方法

9.1進?程?與?線?程

進程是程序的一次動態(tài)執(zhí)行過程,它經(jīng)歷了從代碼加載、執(zhí)行到執(zhí)行完畢的一個完整過程。這個過程也是進程本身從產(chǎn)生、發(fā)展到最終消亡的過程。多進程操作系統(tǒng)能同時運行多個進程(程序),由于CPU具備分時機制,所以每個進程都能循環(huán)獲得自己的CPU時間片。由于CPU執(zhí)行速度非???,使得所有程序好像是在“同時”運行一樣。

線程是比進程更小的執(zhí)行單位,線程是進程內(nèi)部單一的一個順序控制流。所謂多線程,是指一個進程在執(zhí)行過程中可以產(chǎn)生多個線程,這些線程可以同時存在、同時運行,形成多條執(zhí)行線索。一個進程可能包含了多個同時執(zhí)行的線程。

多線程是實現(xiàn)并發(fā)機制的一種有效手段。進程和線程一樣,都是實現(xiàn)并發(fā)的一個基本單位。

線程和進程的主要差別體現(xiàn)在以下兩個方面:

(1)同樣作為基本的執(zhí)行單元,線程是劃分得比進程更小的執(zhí)行單位。

(2)每個進程都有一段專用的內(nèi)存區(qū)域。與此相反,線程卻共享內(nèi)存單元(包括代碼和數(shù)據(jù)),通過共享的內(nèi)存單元來實現(xiàn)數(shù)據(jù)交換、實時通信與必要的同步操作。多線程的應(yīng)用范圍很廣。在一般情況下,程序的某些部分同特定的事件或資源聯(lián)系在一起,同時又不想為它暫停程序其他部分的執(zhí)行,在這種情況下,就可以考慮創(chuàng)建一個線程,令它與那個事件或資源關(guān)聯(lián)到一起,并讓其獨立于主程序運行。通過使用線程,可以避免用戶在運行程序和得到結(jié)果之間停頓,還可以讓一些任務(wù)(如打印任務(wù))在后臺運行,用戶則在前臺繼續(xù)完成其他工作??傊?,利用多線程技術(shù),編程人員可以方便地開發(fā)出能同時處理多個任務(wù)的功能強大的應(yīng)用程序。

9.2認識線程

在傳統(tǒng)的程序語言中,運行的順序總是必須順著程序的流程來走,遇到if-else語句就加以判斷,遇到for、while等循環(huán)會多繞幾個圈,最后程序還是按著一定的程序走,且一次只能運行一個程序塊。Java的“多線程”打破了這種傳統(tǒng)的束縛。例如,有些包含循環(huán)的線程可能要使用比較長的一段時間來運算,此時便可讓另一個線程來做其他處理。本節(jié)將用一個簡單的程序來說明單一線程與多線程的區(qū)別。ThreadDemo9_1是單一線程的范例,其程序代碼編寫方法與前幾節(jié)的程序代碼并沒有什么不同。

【例9-1】ThreadDemo9_1.java。程序說明:

(1)第15~21行定義了run()方法,用循環(huán)輸出10個連續(xù)的字符串。

(2)第5行創(chuàng)建TestThread對象之后調(diào)用run()方法,輸出“TestThread在運行”,最后執(zhí)行main()方法中的循環(huán),輸出“main線程在運行”。

從本例中可看出,要想運行main()方法中的循環(huán),必須要等TestThread類中的run()方法執(zhí)行完之后才可以運行,這便是單一線程的缺陷。在Java中,是否可以同時運行第9行與第19行的語句,使得“main線程在運行”和“TestThread在運行”交錯輸出呢?答案是肯定的,其方法是:在Java中激活多個線程。那么,該如何激活線程呢?如果在類里要激活線程,必須先做好下面兩個準備:

(1)線程必須擴展自Thread類,使自己成為它的子類。

(2)線程的處理必須編寫在run()方法內(nèi)。

9.2.1通過繼承Thread類實現(xiàn)多線程

Thread存放在java.lang類庫中,但并不需加載java.lang類庫,因為它會自動加載。此外,run()方法是定義在Thread類中的一個方法,因此把線程的程序代碼編寫在run()方法內(nèi),事實上所做的就是覆蓋操作。因此要使一個類可激活線程,必須按照下面的語法來編寫:從運行結(jié)果中可以發(fā)現(xiàn),兩行輸出是交替進行的。也就是說,程序是采用多線程機制運行的。與之前的程序相比,修改后的程序第13行TestThread類繼承了Thread類,第5行調(diào)用的不再是run()方法,而是start()方法。所以,要啟動線程,必須調(diào)用Thread類之中的start()方法,而調(diào)用了start()方法,也就是調(diào)用了run()方法。

9.2.2通過實現(xiàn)Runnable接口實現(xiàn)多線程

Java程序只允許單一繼承,即一個子類只能有一個父類,所以在Java中如果一個類繼承了某一個類,同時又想采用多線程技術(shù),就不能用Thread類產(chǎn)生線程,因為Java不允許多繼承,這時就要用Runnable接口來創(chuàng)建線程。程序說明:

(1)第5行實例化一個TestThread類的對象。

(2)第6行通過TestThread類(Runnable接口的子類)去實例化一個Thread類的對象,之后調(diào)用start()方法啟動多線程。

(3)第14行TestThread類實現(xiàn)了Runnable接口,同時復寫了Runnable接口之中的run()方法。也就是說,此類為一多線程實現(xiàn)類。

從輸出結(jié)果可以發(fā)現(xiàn),無論繼承了Thread類還是實現(xiàn)了Runnable接口,運行結(jié)果都是一樣的。為什么實現(xiàn)了Runnable接口還需要調(diào)用Thread類中的start()方法才能啟動多線程呢?通過查找JDK文檔就可以發(fā)現(xiàn),在Runnable接口中只有一個run()方法,如圖9-1所示。圖9-1Runnable接口中的方法列表從圖9-1中可以看出,在Runnable接口中并沒有start()方法,所以一個類實現(xiàn)了Runnable接口也必須用Thread類中的start()方法來啟動多線程。這點可以通過查找JDK文檔中的Thread類知道。在Thread類之中,有這樣一個構(gòu)造方法:

publicThread(Runnabletarget)

由此構(gòu)造方法可以看出,可以將一個Runnable接口的實例化對象作為參數(shù)去實例化Thread類對象。在實際開發(fā)中,希望讀者盡可能使用Runnable接口去實現(xiàn)多線程機制。9.2.3兩種多線程實現(xiàn)機制的比較

由9.2.1節(jié)和9.2.2節(jié)可以看出,不管實現(xiàn)了Runnable接口還是繼承了Thread類其結(jié)果都是一樣的,那么這兩者之間有什么關(guān)系呢?讀者可以通過查看JDK文檔發(fā)現(xiàn)二者之間的聯(lián)系,如圖9-2所示。圖9-2Thread類與Runnable接口的關(guān)系從圖9-2中可以看出,Thread類實現(xiàn)了Runnable接口。也就是說,Thread類也是Runnable接口的一個子類。那么兩者之間除了這些聯(lián)系之外還有什么區(qū)別呢?下面通過編寫一個應(yīng)用程序來進行比較分析。下面程序是一個模擬鐵路售票系統(tǒng)的范例,實現(xiàn)四個售票點發(fā)售某日某次列車的車票20張,一個售票點用一個線程來表示。

【例9-4】ThreadDemo9_3.java。下例由ThreadDemo9_3的程序修改而成,這里讓main()方法中產(chǎn)生四個線程。

【例9-5】

修改后的ThreadDemo9_3.java。由于程序的輸出結(jié)果過長,所以只截取了后面一部分,但從這部分輸出結(jié)果中可以看出,這里啟動了四個線程對象,這四個線程對象各自占有各自的資源,所以可以得出結(jié)論:用Thread類實際上無法達到資源共享的目的。

那么實現(xiàn)Runnable接口會如何呢?下面這個例子也修改自ThreadDemo9_3,讀者可以觀察一下輸出結(jié)果。

【例9-6】ThreadDemo9_4.java。從上面的程序中可以看出,第7行到第10行啟動了四個線程,從程序的輸出結(jié)果來看,盡管啟動了四個線程對象,但是結(jié)果都操縱了同一個資源,實現(xiàn)了資源共享的目的。

可見,實現(xiàn)Runnable接口相對于繼承Thread類來說具有如下顯著優(yōu)勢:

(1)適合多個相同程序代碼的線程去處理同一資源的情況,把虛擬CPU(線程)同程序的代碼、數(shù)據(jù)有效分離,較好地體現(xiàn)了面向?qū)ο蟮脑O(shè)計思想。

(2)可以避免由于Java的單繼承特性帶來的局限。開發(fā)中經(jīng)常碰到這樣一種情況,即:當要將已經(jīng)繼承了某一個類的子類放入多線程中時,由于一個類不能同時有兩個父類,所以不能用繼承Thread類的方式,那么就只能采用實現(xiàn)Runnable接口的方式。

(3)增強了程序的健壯性,代碼能夠被多個線程共享,代碼與數(shù)據(jù)是獨立的。當多個線程的執(zhí)行代碼來自同一個類的實例時,稱它們共享相同的代碼。多個線程可以操作相同的數(shù)據(jù),與它們的代碼無關(guān)。當共享訪問相同的對象時,共享相同的數(shù)據(jù)。當線程被構(gòu)造時,需要的代碼和數(shù)據(jù)通過一個對象作為構(gòu)造函數(shù)實參傳遞進去,這個對象就是一個實現(xiàn)了Runnable接口的類的實例。

事實上,幾乎所有多線程應(yīng)用都可用第二種方式,即實現(xiàn)Runnable接口。

9.3線?程?的?狀?態(tài)

每個Java程序都有一個缺省的主線程。對于Java應(yīng)用程序,主線程是main()方法執(zhí)行的線索;對于Applet程序,主線程是指揮瀏覽器加載并執(zhí)行JavaApplet程序的線索。要想實現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對象。任何線程一般具有五種狀態(tài),即創(chuàng)建、就緒、運行、阻塞、終止。線程狀態(tài)的轉(zhuǎn)移與方法之間的關(guān)系可用圖9-3來表示。圖9-3線程的狀態(tài)轉(zhuǎn)換

1.創(chuàng)建狀態(tài)

在程序中用構(gòu)造方法創(chuàng)建了一個線程對象后,新的線程對象便處于新建狀態(tài),此時它已經(jīng)有了相應(yīng)的內(nèi)存空間和其他資源,但還處于不可運行狀態(tài)。創(chuàng)建一個線程對象可采用線程構(gòu)造方法來實現(xiàn),如Threadthread=newThread();。

2.就緒狀態(tài)

創(chuàng)建線程對象后,調(diào)用該線程的start()方法就可以啟動線程。當線程啟動時,線程進入就緒狀態(tài)。此時,線程將進入線程隊列排隊,等待CPU服務(wù),這表明它已經(jīng)具備了運行

條件。

3.運行狀態(tài)

當就緒狀態(tài)的線程被調(diào)用并獲得處理器資源時,線程就進入了運行狀態(tài)。此時,自動調(diào)用該線程對象的run()方法。run()方法定義了該線程的操作和功能。

4.阻塞狀態(tài)

一個正在執(zhí)行的線程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時的輸入/輸出操作時,將讓出CPU并暫時中止自己的執(zhí)行,進入阻塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調(diào)用sleep()、suspend()、wait()等方法,線程都將進入阻塞狀態(tài)。阻塞時,線程不能進入排隊隊列,只有當引起阻塞的原因被消除后,線程才可以轉(zhuǎn)入就緒狀態(tài)。

5.終止狀態(tài)

線程調(diào)用stop()方法時或run()方法執(zhí)行結(jié)束后,線程即處于終止狀態(tài)。處于終止狀態(tài)的線程不具有繼續(xù)運行的能力。

9.4線程操作的一些方法

在Java實現(xiàn)多線程的程序中,雖然Thread類實現(xiàn)了Runnable接口,但是操作線程的主要方法并不在Runnable接口中,而是在Thread類中。表9-1列出了Thread類中的主要方法。表9-1Thread類中的主要方法9.4.1取得和設(shè)置線程的名稱

在Thread類中,可以通過getName()方法取得線程的名稱,通過setName()方法設(shè)置線程的名稱。線程的名稱一般在啟動線程前設(shè)置,但也允許為已經(jīng)運行的線程設(shè)置名稱。允許兩個Thread對象有相同的名字,但為了清晰,應(yīng)該盡量避免這種情況的發(fā)生。

另外,如果程序并沒有為線程指定名稱,則系統(tǒng)會自動為線程分配一個名稱。

【例9-7】GetNameThreadDemo.java。程序說明:

(1)第1行聲明一個GetNameThreadDemo類,此類繼承自Thread類,之后3~7行復寫Thread類中的run()方法。

(2)第8~14行聲明一個printMsg()方法,此方法用于取得當前線程的信息。在第11行,通過Thread類中的currentThread()方法,返回一個Thread類的實例化對象。由表9-1可知,此方法返回當前正在運行的線程,即返回正在調(diào)用此方法的線程。第12行通過調(diào)用Thread類中的getName()方法,返回當前運行線程的名稱。

(3)第6行和第21行分別調(diào)用了printMsg()方法,但第6行從多線程的run()方法中調(diào)用,而第21行從main()方法中調(diào)用。為什么程序中輸出的運行線程的名稱中會有一個main呢?這是因為main()方法也是一個線程,實際上在命令行中運行Java命令時,就啟動了一個JVM的進程,默認情況下此進程會產(chǎn)生兩個線程:一個是main()方法線程,另外一個就是垃圾回收(GC)線程。

下例介紹如何在線程中設(shè)置線程的名稱。

【例9-8】SetNameThreadDemo.java。9.4.2線程是否啟動的判斷

通過Thread類中的start()方法通知線程規(guī)劃器這個新線程已準備就緒,而且應(yīng)當在規(guī)劃器的最早方便時間調(diào)用它的run()方法。在程序中也可以通過isAlive()方法來測試線程是否已經(jīng)啟動而且仍然在啟動。

【例9-9】StartThreadDemo.java。程序說明:

(1)第20行在線程運行之前調(diào)用isAlive()方法,判斷線程是否啟動,但在此處并沒有啟動,所以返回“false”,表示線程未啟動。

(2)第22行在啟動線程之后調(diào)用isAlive()方法,此時線程已經(jīng)啟動,所以返回“true”。

(3)第28行在main()方法快結(jié)束時調(diào)用isAlive()方法,此時的狀態(tài)不再固定,有可能是true,也有可能是false。9.4.3后臺線程與setDaemon()方法

對Java程序來說,只要還有一個前臺線程在運行,這個進程就不會結(jié)束;如果一個進程中只有后臺線程在運行,這個進程就會結(jié)束。前臺線程是相對于后臺線程而言的,前面所介紹的線程都是前臺線程。那么什么樣的線程是后臺線程呢?如果某個線程對象在啟動(調(diào)用start()方法)之前調(diào)用了setDaemon(true)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論