第7章多線程編程_第1頁
第7章多線程編程_第2頁
第7章多線程編程_第3頁
第7章多線程編程_第4頁
第7章多線程編程_第5頁
已閱讀5頁,還剩98頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第7章多線程編程

本章主要內(nèi)容:

.線程的基本概念;

.Thread類和Runnable接口;

.線程的控制;

.線程的同步;

.線程綜合示例。

2023/2/67.1線程的基本概念進程(Process)和線程(Thread)是現(xiàn)代操作系統(tǒng)中兩個必不可少的運行模型。操作系統(tǒng)可以運行多個進程,而每個一個進程中又可以創(chuàng)建一個或多個線程。進程通常被區(qū)分為系統(tǒng)進程和用戶進程,而每個Java程序可看成是一個用戶進程,并可用Java語言提供的Thread類創(chuàng)建一個或多個線程,充分利用軟硬件資源。7.1.1線程線程是操作系統(tǒng)中重要概念之一,是程序運行的基本執(zhí)行單元。在絕大多數(shù)平臺上,Java程序是直接利用操作系統(tǒng)的中線程來運行的。7.1線程的基本概念【例7-1】線程執(zhí)行示例的源代碼。publicclassEx7_1_UnderstandThread{ publicstaticvoidmain(String[]args){ MyThreadmyThread1=newMyThread(); myThread1.start(); MyThreadmyThread2=newMyThread(); myThread2.start(); for(inti=0;i<10;i++) System.out.print("主函數(shù)第"+(i+1)+"次輸出!"); }}7.1線程的基本概念classMyThreadextendsThread{ publicvoidrun(){ for(inti=0;i<10;i++) System.out.print(this.getName()+"第"+(i+1)+"次輸出!"); }}7.1線程的基本概念運行結(jié)果為:7.1線程的基本概念由上例可知,主線程(主函數(shù))和兩個線程類實例(Thread-0與Thread-1)交替輸出內(nèi)容。事實上,如果修改主線程或線程輸出內(nèi)容,各線程執(zhí)行時間也隨之變化,運行結(jié)果也有所變化,往往是主線程和線程類實例交替輸出不定次,或者是主線程完全輸出完成后,才輪到線程類實例運行而產(chǎn)生輸出。一般來說,創(chuàng)建線程的線程稱為父線程,而由它創(chuàng)建的線程則稱為子線程,父線程往往是主線程。7.1線程的基本概念7.1.2使用線程的優(yōu)勢充分利用CPU資源:CPU空閑時,可以立刻調(diào)入另一線程;簡化編程模型:對于一些復(fù)雜的任務(wù),將各子任務(wù)由單獨的線程完成,有助于開發(fā)人員理解程序結(jié)構(gòu)和簡化開發(fā),降低維護成本;簡化異步事件的處理:不同線程可以負責(zé)處理不同的事件,不會因為某一事件未處理或未處理完成而無法響應(yīng)其他事件,有利于提高I/O應(yīng)用程序效率;7.1線程的基本概念7.1.2使用線程的優(yōu)勢使GUI程序更有效率:單線程處理GUI事件(如鼠標單擊事件)時,必須采用循環(huán)來不斷掃描隨時可能發(fā)生的GUI事件,當某次GUI事件需要較長時間處理完時,這有可能延誤及時處理后續(xù)GUI事件,而且界面會表現(xiàn)出“凍結(jié)”狀態(tài),而用多線程可以極快地處理GUI事件;節(jié)約成本:使用多線程是提高程序執(zhí)行效率的方法之一,既不需要增加硬件,又比多進程方式更容易實現(xiàn)數(shù)據(jù)共享(在同一個進程上下文中),是最廉價的提高程序性能的方法。7.1線程的基本概念7.1.3線程的狀態(tài)

Java語言是使用Thread類及其子類的對象來表示線程7.1線程的基本概念7.1.4線程模型進程是正在執(zhí)行的程序。一個或多個線程構(gòu)成了一個進程。一個線程(即執(zhí)行上下文)由三個部分組成:處理機、代碼和數(shù)據(jù)。線程的運行過程為:占用CPU,執(zhí)行特定的程序代碼,該程序代碼操縱內(nèi)存中特定數(shù)據(jù)。在Java平臺中,類java.lang.Thread中封裝了一個虛擬處理機,它控制整個線程的運行。不同線程可以共享的方式訪問同一個公共對象,實現(xiàn)數(shù)據(jù)交換。7.1線程的基本概念多線程程序是指在一個程序內(nèi)實現(xiàn)了并發(fā)執(zhí)行的一組代碼。實際上,編程語言一般提供的是串行程序設(shè)計的方法(指順序結(jié)構(gòu)、選擇結(jié)構(gòu)和循環(huán)結(jié)構(gòu)),而計算機的并發(fā)能力由操作系統(tǒng)提供。在Java平臺中,Thread類中封裝的虛擬處理機使其在語言級提供了多線程并發(fā)的概念,通過實例化多個Thread類,可實現(xiàn)多線程編程,這為編寫多線程程序提供了極大的方便。7.1線程的基本概念Java語言中,可以通過繼承線程類Thread或?qū)崿F(xiàn)Runnable接口來創(chuàng)建用戶自定義的線程。主要內(nèi)容:

本小節(jié)主要介紹如何繼承Thread類編寫用戶自己的線程類和如何通過實現(xiàn)Runnable接口來創(chuàng)建線程。7.2創(chuàng)建線程7.2.1繼承Thread類編寫并指定線程需要執(zhí)行的方法;啟動一個線程。線程類Thread中包含了實現(xiàn)上述功能的兩個方法:run():包含線程運行時所執(zhí)行的代碼;start():用于啟動線程。Java平臺中。線程類Thread是在java.lang包中定義的,但線程核心的內(nèi)容并非定義在這個類中。在這種模式下,使用線程時只需注意以下兩點:7.2創(chuàng)建線程用戶的線程類必須繼承自Thread類(或?qū)崿F(xiàn)Runnable接口),并覆蓋Thread類的run()方法(或?qū)崿F(xiàn)Runnable接口中的run()方法)。在Thread類中,run()方法的定義如下:

publicvoidrun(){//用戶可以加入自己的代碼。

}7.2創(chuàng)建線程用戶定義好自己的線程類MyThread后還必須實例化,并用start()方法啟動。例7-1就是按照上述流程實現(xiàn)線程編程的。Ex7_1_UnderstandThread類中包含用戶自定義的線程類MyThread和主函數(shù)main()。其中,MyThread繼承自Thread類,并覆蓋了run()方法,用戶定義了新的功能(循環(huán)輸出字符串);而主函數(shù)main()首先創(chuàng)建了MyThead實例,并用start()啟動線程,接下來是主函數(shù)的其他功能,即循環(huán)輸出。事實上,這個程序包含兩個線程,即用戶自定義的線程和主函數(shù)線程,分別循環(huán)輸出字符串。線程類Thread除了包含run()和start()方法外,還包含一個不帶任何參數(shù)的構(gòu)造方法,因此,例7-1創(chuàng)建線程時,沒帶任何參數(shù)。這使得編寫線程程序變得十分簡單。7.2創(chuàng)建線程例7-2所示的是一個典型的多線程示例。Ex7_2_TestThread繼承自Thread類,并定義了一個構(gòu)造方法Ex2_TestThread(),實現(xiàn)了傳入線程名字和輸出的功能。該類同樣覆蓋了run()方法,輸出線程名后讓線程休眠一段時間(隨機函數(shù)決定長短)。與例7-1最大的區(qū)別是,該線程類中包含主函數(shù),并且主函數(shù)創(chuàng)建了2個線程實例,先后啟動執(zhí)行。顯然該程序一共啟動三個線程,即主函數(shù)線程,“如來”線程和“孫悟空”線程。7.2創(chuàng)建線程【例7-2】繼承Thread類示例的源代碼。publicclassEx7_2_TestThreadextendsThread{ StringthreadName; publicEx7_2_TestThread(StringthreadName) { System.out.println("本線程的名字:"+threadName); this.threadName=threadName; } publicvoidrun() { for(inti=0;i<3;i++) {System.out.println("正在運行的線程是"+threadName); try{ Thread.sleep((int)(Math.random()*1000)); } catch(InterruptedExceptionex) { System.err.println(ex.toString()); } }//for }//runpublicstaticvoidmain(String[]args) { System.out.println("開始運行主函數(shù)!"); Ex7_2_TestThreadthread1=newEx7_2_TestThread("如來"); Ex7_2_TestThreadthread2=newEx7_2_TestThread("孫悟空"); thread1.start(); thread2.start(); System.out.println("主函數(shù)運行結(jié)束!"); }//main()}7.2創(chuàng)建線程開始運行主函數(shù)!本線程的名字:如來本線程的名字:孫悟空主函數(shù)運行結(jié)束!正在運行的線程是如來正在運行的線程是孫悟空正在運行的線程是孫悟空正在運行的線程是孫悟空正在運行的線程是如來正在運行的線程是如來7.2創(chuàng)建線程從上述運行結(jié)果可以看出,主函數(shù)線程被先調(diào)入內(nèi)存,并由Java平臺啟動,因此,它先輸出結(jié)果,而后創(chuàng)建并啟動2個線程,這時由于它還未被調(diào)出CPU,因此立刻輸出“主函數(shù)運行結(jié)束!”,而后才是2個線程輪流執(zhí)行,輸出本線程的名字。由于系統(tǒng)為每個線程分配的時間片不固定,線程的執(zhí)行時間也不固定,因此,每次執(zhí)行程序的結(jié)果不一定相同。7.2創(chuàng)建線程7.2.2實現(xiàn)Runnable接口問題:

由于Java語言不支持多繼承,一個類如果為了使用線程而繼承自Thread類就不能再繼承其他類了,這樣很難滿足實際應(yīng)用的需要。解決:Java語言提供了接口技術(shù),通過實現(xiàn)一個或多個接口就能解決這一難題。7.2創(chuàng)建線程在java.lang包中有一個Runnable接口,通過實現(xiàn)這個接口也能實現(xiàn)線程編程。該接口只有一個抽象方法voidrun(),用于實現(xiàn)線程要執(zhí)行的代碼。實現(xiàn)Runnable接口的類并不能直接作為線程運行,還需線程類Thread配合才能執(zhí)行。Thread類中有一個類型為Runnable的屬性,名為target。Thread類中的run()方法用到了這個屬性,并調(diào)用該屬性的run()方法,達到執(zhí)行線程的目的,即Thread類的run()方法按如下代碼實現(xiàn):7.2創(chuàng)建線程從上述代碼可以看出,如果將實現(xiàn)了Runnable接口的類的實例傳給target屬性,那么就可以通過線程類Thread的實例來達到啟動線程的目的。事實上,Thread類提供了5個構(gòu)造函數(shù)都可以為target屬性賦值,例如可用構(gòu)造方法Thread(RunnablerunnableObject)給target屬性賦值。

由上可知,使用Runnable接口創(chuàng)建線程的步驟可以總結(jié)如下:實現(xiàn)Runnable接口,如實現(xiàn)了該接口的類為MyRunnable,并在MyRunnable類的run()方法里編寫想讓線程執(zhí)行的代碼;創(chuàng)建實現(xiàn)了Runnable接口類的實例,如創(chuàng)建MyRunnable類的實例為myRunnable;創(chuàng)建線程類Thread的實例,并用構(gòu)造方法Thread(Runnable)將myRunnable賦值給target。

經(jīng)過上述三步后,就得到了線程類實例,調(diào)用start()方法后就啟動這個線程了。這個線程實際上是執(zhí)行MyRunnable類中的run()方法的代碼。

按照上述方法稍加修改例7-2可得到利用Runnable接口創(chuàng)建線程的完整過程。7.2創(chuàng)建線程【例7-3】實現(xiàn)Runnable接口示例的源代碼。publicclassEx7_3_MyRunnableimplementsRunnable{ StringthreadName;

publicEx7_3_MyRunnable(StringthreadName){ System.out.println("本線程的名字:"+threadName); this.threadName=threadName; }7.2創(chuàng)建線程 publicvoidrun(){ for(inti=0;i<3;i++){ System.out.println("正在運行的線程是"+threadName); try{ Thread.sleep((int)(Math.random()*1000)); }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } }//for }//run7.2創(chuàng)建線程 publicstaticvoidmain(String[]args){ System.out.println("開始運行主函數(shù)!"); Ex7_3_MyRunnablemyRunnable1=newEx7_3_MyRunnable("如來"); Ex7_3_MyRunnablemyRunnable2=newEx7_3_MyRunnable("孫悟空"); Threadthread1=newThread(myRunnable1); Threadthread2=newThread(myRunnable2); thread1.start(); thread2.start(); System.out.println("主函數(shù)運行結(jié)束!"); }//main()}7.2創(chuàng)建線程

運行結(jié)果和例7-2完全類似,但會因MyRunnable類中run()方法執(zhí)行的時間或長點或短點(休眠的時間是隨機的)而導(dǎo)致輸出順序與例7-2的輸出順序不一致。

為了更好地理解用Runnable接口編寫多線程的應(yīng)用,例7-4展現(xiàn)了子類繼承父類時實現(xiàn)Runnable接口的情況。在該例中首先定義了一個Student類,接著定義了繼承自Student類的Master類,并實現(xiàn)了Runnable接口。7.2創(chuàng)建線程【例7-4】子類實現(xiàn)Runnable接口示例的源代碼。publicclassEx7_4_UseRunnable{

publicstaticvoidmain(String[]args){

Mastermaster=newMaster("如來"); Threadthread=newThread(master); thread.start();

}}

7.2創(chuàng)建線程classMasterextendsStudentimplementsRunnable{ Master(StringName){ super(Name); } publicvoidprintInformation(){//覆蓋父類的方法,實現(xiàn)特定的功能。 System.out.println("我是一名研究生!我叫"+this.Name); } publicvoidrun(){ printInformation(); }}classStudent{ StringName; publicStudent(StringName){ this.Name=Name; }

publicvoidprintInformation(){ System.out.println("我是一名大學(xué)生!我叫"+this.Name); }}7.2創(chuàng)建線程【例7-5】繼承Thread派生類示例的源代碼。publicclassEx7_5_TestExtendsThread{ publicstaticvoidmain(String[]args){

Master1master1=newMaster1("如來"); master1.start(); //Student1student1=newStudent1("孫悟空"); //student1.start(); }}7.2創(chuàng)建線程classMaster1extendsStudent1{ Master1(StringName){ super(Name); }

publicvoidprintInformation(){//覆蓋父類的方法,實現(xiàn)特定的功能。 System.out.println("我是一名研究生!我叫"+this.Name); }}7.2創(chuàng)建線程classStudent1extendsThread{ StringName;

publicStudent1(StringName){ this.Name=Name; }

7.2創(chuàng)建線程 publicvoidprintInformation(){ System.out.println("我是一名大學(xué)生!我叫"+this.Name); }

publicvoidrun(){ printInformation(); }}7.2創(chuàng)建線程7.2創(chuàng)建線程特別要注意的是,在實際應(yīng)用中,要清晰知道應(yīng)在線程類Thread的哪個子孫類中覆蓋run()方法和具體應(yīng)實現(xiàn)什么樣的功能,以免無法掌控線程的執(zhí)行。對于初學(xué)者來說,不鼓勵通過繼承Thread的派生類方式支持線程特性,而建議采用希望支持線程特性的類直接實現(xiàn)Runnable接口的方式達到這一目的(即例7-4采用的方式)7.2創(chuàng)建線程7.3深入學(xué)習(xí)Thread類

本節(jié)將繼續(xù)深入介紹Thread類的一些重要方法和屬性,包括線程的名字、優(yōu)先級以及調(diào)度等等。7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.3.2設(shè)置優(yōu)先級7.3深入學(xué)習(xí)Thread類【例7-6】優(yōu)先級設(shè)置示例的源代碼。publicclassEx7_6_ChangeThreadPriorityextendsThread{ StringthreadName; publicEx7_6_ChangeThreadPriority(StringthreadName) { System.out.println("本線程的名字:"+threadName); this.threadName=threadName; System.out.println("創(chuàng)建線程\""+this.threadName+"\"時的優(yōu)先級是"+this.getPriority()); } publicvoidrun() { System.out.println("正在運行的線程\""+this.threadName+"\"的優(yōu)先級是"+this.getPriority()); }//run7.3深入學(xué)習(xí)Thread類

publicstaticvoidmain(String[]args) { System.out.println("開始運行主函數(shù)!"); Ex7_6_ChangeThreadPrioritythread1=newEx7_6_ChangeThreadPriority("如來"); Ex7_6_ChangeThreadPrioritythread2=newEx7_6_ChangeThreadPriority("孫悟空"); thread1.start(); thread1.setPriority(Thread.MIN_PRIORITY); thread2.start(); thread2.setPriority(MAX_PRIORITY); System.out.println("主函數(shù)運行結(jié)束!"); }//main()}7.3深入學(xué)習(xí)Thread類從上述結(jié)果不難看出,主函數(shù)線程的優(yōu)先級為一般,創(chuàng)建的子線程都繼承了這一優(yōu)先級,即輸出為5,而在線程啟動后,子線程的優(yōu)先級分別被調(diào)整成最低和最高。事實上,每個Java線程的優(yōu)先級都在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間,即1到10之間,而每個新線程默認優(yōu)先級為Thread.NORM_PRIORITY。7.3深入學(xué)習(xí)Thread類為了讓優(yōu)先級較高的線程優(yōu)先執(zhí)行,系統(tǒng)按線程的優(yōu)先級調(diào)度,具有高優(yōu)先級的線程會在較低優(yōu)先級的線程之前得到執(zhí)行。如本例中,后創(chuàng)建的“孫悟空”線程因被設(shè)置成最高優(yōu)先級而優(yōu)先于先創(chuàng)建的“如來”線程執(zhí)行。多個線程運行時,線程調(diào)度是搶先式的,即如果當前線程在執(zhí)行過程中,一個具有更高優(yōu)先級的線程進入可執(zhí)行狀態(tài),則該高優(yōu)先級的線程會被立即調(diào)度執(zhí)行。若線程的優(yōu)先級相同,線程在就緒隊列中排隊。在分時系統(tǒng)中,每個線程按時間片輪轉(zhuǎn)方式執(zhí)行。在某些平臺上線程調(diào)度將會隨機選擇一個線程,或始終選擇第一個可以得到的線程。因此,合理設(shè)置線程的優(yōu)先級能使程序運行更高效。7.3深入學(xué)習(xí)Thread類7.3.3線程的名字Thread類有一個類型為String的name屬性用于存儲線程的名字,另外有StringgetName()和voidsetName(String)兩個方法來設(shè)置或獲取這個屬性的值。另外,Thread類還提供了相應(yīng)的構(gòu)造方法,在創(chuàng)建對象時就可以指定線程的名字,具體如下:1.Thread(Stringname):接受一個String類型的變量作為線程的名字;2.Thread(Runnabletarget,Stringname):接受一個Runnable實例和一個String實例作為參數(shù)。前者的目的是傳入要執(zhí)行的代碼,即run()方法,后者則傳入線程名字。

如果在創(chuàng)建線程是未指定名字,默認名字一般是“Thread-”加上一個遞增的整數(shù);對于主線程來說,它的默認名字一般會被設(shè)置為main。

7.3深入學(xué)習(xí)Thread類【例7-7】線程名字示例的源代碼。publicclassEx7_7_ShowThreadName{ publicstaticvoidmain(String[]args){ ShowThreadNamedefaultName=newShowThreadName(); ShowThreadNamename=newShowThreadName("如來"); defaultName.start(); name.start(); }//main}//publicclassEx7_7_ShowThreadName

7.3深入學(xué)習(xí)Thread類classShowThreadNameextendsThread{ publicShowThreadName(){ super(); } publicShowThreadName(Stringname){ super(name); } publicvoidrun(){ System.out.println("這個線程的名字是:"+this.getName()); }}//classShowThreadName7.3深入學(xué)習(xí)Thread類

運行結(jié)果為:

這個線程的名字是:如來

這個線程的名字是:Thread-0

上述程序啟動2個線程,第一個是輸出默認線程名,第二個是輸出用戶設(shè)定的線程名。需要注意的是兩個線程輸出結(jié)果與語句順序相反。7.3深入學(xué)習(xí)Thread類7.3.4得到當前線程

Java代碼是由某一Java線程執(zhí)行的,Thread類提供一個靜態(tài)方法currentThread()用于獲得這一線程,以便進一步控制線程的執(zhí)行。該方法的返回值是Thread的引用,這個引用所指向的Thread類的實例正是“執(zhí)行當前代碼的線程”。7.3深入學(xué)習(xí)Thread類【例7-8】當前線程示例的源代碼。publicclassEx7_8_CurrentThreadName{ publicstaticvoidmain(String[]args){ Threadthread=Thread.currentThread(); System.out.println("當前線程的名字是:"+thread.getName()); ShowCurrentThreadNamecthread=newShowCurrentThreadName(); cthread.start(); }}//classEx7_8_CurrentThreadName7.3深入學(xué)習(xí)Thread類classShowCurrentThreadNameextendsThread{ publicvoidrun(){ System.out.println("這個線程的名字是:"+this.getName()); Threadthread=Thread.currentThread(); System.out.println("當前線程的名字是:"+thread.getName()); }}//classShowCurrentThreadName7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類上述程序首先調(diào)用CurrentThreadName()方法得到主線程的引用,而后調(diào)用getName()方法輸出名字,而后創(chuàng)建ShowCurrentThreadName類的實例并啟動,該類繼承自Thread,在run()方法中調(diào)用CurrentThreadName()方法得到該線程的名字并輸出。上述示例雖然演示了得到當前線程的方法如何使用,但并未講清這一方法的具體應(yīng)用場合和作用,目前至少還存在這么一個疑問:自己寫的程序難道不知道正被哪個線程執(zhí)行嗎?何必多此一舉用這個方法確定呢?事實上,多線程編程時,如果要創(chuàng)建一組協(xié)調(diào)工作的線程,而這些線程中的一部分又由同一線程類的派生類創(chuàng)建而成,那么同時工作的若干個線程運行代碼是相同的,即會執(zhí)行同一個run()方法。若為了使某一特定線程執(zhí)行一些特定的任務(wù),則可在run()方法中得到當前線程,進而加以控制。7.3深入學(xué)習(xí)Thread類7.3.5線程的休眠Thread類還有另外一個靜態(tài)方法sleep(),可用于讓線程沉睡若干毫秒。它沒有返回值,只接受一個long類型的參數(shù),這個參數(shù)就是傳入讓線程沉睡的毫秒數(shù)。例如,參數(shù)為1000時,sleep()方法的執(zhí)行結(jié)果就是讓當前線程沉睡1秒鐘,而1秒后,線程會自動蘇醒,并繼續(xù)執(zhí)行后續(xù)的代碼。當然這里所說的“沉睡”就是前面介紹的線程狀態(tài)之一,即線程會轉(zhuǎn)入“被掛起”或者“掛起”狀態(tài),而當沉睡時間到時,線程又會被轉(zhuǎn)為“運行”狀態(tài)。顯然,并不是嚴格地沉睡1秒鐘,實際上可能被掛起1.001秒或更長點,而這對一般的應(yīng)用程序來說,時間控制的精度足夠了,需要注意的是在高速數(shù)據(jù)采集等時間精度要求極高的場合,需選用其他方法確保時間的精度。7.3深入學(xué)習(xí)Thread類

sleep()方法并非一定能夠運行成功,當線程處在掛起狀態(tài)時,由于某種原因被打斷了,它會拋出一個類型為InterruptedException的異常。例如,以10000為參數(shù)執(zhí)行sleep()方法時,線程應(yīng)該掛起10秒鐘左右,但是當線程被掛起8秒鐘以后,因某種原因被打斷了,那么就會拋出這個異常,即該線程只沉睡了約8秒鐘。如果沒特殊的需求,這個異常沒必要向外傳遞,用try-catch語句直接處理即可。

可參見例7-2使用Sleep()方法,即用如下格式調(diào)用即可。7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.3.6簡單控制線程7.3深入學(xué)習(xí)Thread類

除了前面介紹的sleep()方法可以讓當前線程沉睡(休眠或掛起)外,Thread類的其他方法也可以用于控制線程的狀態(tài)。Yield()方法也是一個靜態(tài)方法,可以用來使具有相同優(yōu)先級的線程獲得執(zhí)行機會,即它會暫停當前的線程,并將其放入可運行隊列,而選同優(yōu)先級的另一線程運行。當然,如果沒有相同優(yōu)先級的可運行線程,yield()將什么也不做,繼續(xù)讓當前線程執(zhí)行。需要注意的是:sleep()調(diào)用會給較低優(yōu)先級的線程一個運行機會,而yield()方法只會給相同優(yōu)先級線程一個執(zhí)行機會。7.3深入學(xué)習(xí)Thread類Thread類提供的join()方法也是用于控制線程的,它的特別之處在于應(yīng)用的場合特殊。如果某線程(線程A)只有在另一線程(線程B)終止時才能繼續(xù)執(zhí)行,則這個線程(線程A)可以調(diào)用另一線程(線程B)的join()方法,將兩個線程“聯(lián)結(jié)”在一起,即線程A先執(zhí)行,而后被掛起,線程B執(zhí)行,直到終止時線程A回到可運行狀態(tài)繼續(xù)執(zhí)行。另外,join(inttime)方法可以傳入一個最多等待時間的參數(shù),用于控制等待時間。7.3深入學(xué)習(xí)Thread類【例7-9】線程聯(lián)結(jié)示例的源代碼。publicclassEx7_9_UseJoin{ publicstaticvoidmain(String[]args){ System.out.println("主線程啟動執(zhí)行,并創(chuàng)建子線程!"); RunThreadrthread=newRunThread(); try{ rthread.join(); //rthread.join(2000);//最多等待2秒鐘 }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } System.out.println("子線程終止,主線程繼續(xù)執(zhí)行!"); }}//Ex7_9_UseJoin7.3深入學(xué)習(xí)Thread類classRunThreadextendsThread{ RunThread(){ start(); }

publicvoidrun(){ System.out.println("子線程的名字是:"+this.getName()+",已開始運行,預(yù)計執(zhí)行3秒!"); try{ Thread.sleep(3*1000); }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } System.out.println("子線程準備運行完畢退出!"); }}7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.4多線程技術(shù)

多線程編程涉及到線程的同步、通訊和死鎖等,Java語言除了提供相關(guān)技術(shù)外,還提供了線程組的概念。本小節(jié)將詳細介紹這些技術(shù),為實現(xiàn)高效可靠的多線程程序提供支持。7.4.1線程同步

由于同一進程的多個線程有時需要共享一個對象,若它們同時訪問該對象,必然會產(chǎn)生訪問共享數(shù)據(jù)的沖突。例如,某一個線程在更新該對象的同時,而另一個線程也試圖更新或讀取該對象,這樣將破壞數(shù)據(jù)的一致性。為避免多個線程同時訪問一個共享對象帶來的訪問沖突問題,Java語言提供了專門的機制來解決,即線程同步。

線程同步可以有效控制多個線程爭搶訪問同一對象的問題,從而避免一個線程剛生成的數(shù)據(jù)又會被其他線程生成的數(shù)據(jù)覆蓋等問題。Java語言是用監(jiān)聽器手段來達到這一目的的。監(jiān)聽器為受保護的資源(共享對象)加一個“訪問鎖”和配一把“鑰匙”,每一要訪問該資源的線程必須先申請“鑰匙”,當?shù)玫借€匙后才能對受保護的資源執(zhí)行操作,而其他線程只能等待,直到它們拿到這把鑰匙。7.4多線程技術(shù)Java語言提供了關(guān)鍵字synchronized來實現(xiàn)多個線程的同步,并區(qū)分為兩種實現(xiàn)方法:一種是方法同步,另一種是對象同步。

方法同步是為了防止多線程訪問同一方法導(dǎo)致數(shù)據(jù)崩潰。具體來說,在定義方法時加上關(guān)鍵字synchronized修飾即可,這能保證某一線程在其他任何線程訪問這一方法前完成一次執(zhí)行,即某一線程一旦啟動對該方法的訪問,其他線程只能等待這個線程執(zhí)行完這個方法后再訪問。定義synchronized方法的格式如下:7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)

事實上,對于一個需要較長時間執(zhí)行的方法來說,其中訪問關(guān)鍵數(shù)據(jù)的時間可能很短,如果將整個方法申明為synchronized,將導(dǎo)致其他線程因無法調(diào)用該方法而長時間無法得到執(zhí)行,這不利于提高程序的運行效率。這時,就可以使用對象同步,只把訪問關(guān)鍵數(shù)據(jù)的代碼段用花括號括起來,在其前面加上synchronized(this)即可。7.4多線程技術(shù)【例7-10】線程同步示例的源代碼。publicclassEx7_10_UseSynchronized{ publicstaticvoidmain(String[]args){ intsize=100; for(intt=0;t<5;t++){ Sumsum=newSum(0); AddOneThread[]rathread=newAddOneThread[size]; for(inti=0;i<size;i++){ try{ rathread[i]=newAddOneThread(sum); }catch(Exceptione){ e.printStackTrace(); } }//for7.4多線程技術(shù)//必須有這段代碼,否則有可能主線程未等到子線程運行完就輸出結(jié)果 for(inti=0;i<size;i++){ try{ rathread[i].join(); }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } } System.out.println("第"+(t+1)+"次,sum="+sum.sum); }//for}//main}//class

7.4多線程技術(shù)classAddOneThreadextendsThread{ Sumsum;

publicAddOneThread(Sumsum){ this.sum=sum; start(); }

publicvoidrun(){ try{ Thread.sleep(500); }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } sum.addOne(); }//run}classSum{ intsum;

publicSum(intsum){ this.sum=sum; }

publicvoidaddOne(){ //synchronized(this){ sum+=1;//} }}7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.4.2線程通信

在一些多線程應(yīng)用中,需要線程之間互相交流和等待,實現(xiàn)互相通信。具體來說,可以通過共享的數(shù)據(jù)做到線程互相交流,通過線程控制方法使線程互相等待。Java語言的java.lang.Object類提供了wait()、notify()和notifyAll()等三個方法來協(xié)調(diào)線程間的運行進度關(guān)系,實現(xiàn)線程通信的。

線程通信是建立在生產(chǎn)者和消費者模型之上的,即一個(組)線程產(chǎn)生輸出(相當于生產(chǎn)產(chǎn)品,該線程稱為生產(chǎn)者,產(chǎn)生一串數(shù)據(jù)流),另一(組)線程進行輸入(相當于消費者,該線程稱為消費者,消耗數(shù)據(jù)流中的數(shù)據(jù)),先有生產(chǎn)者生產(chǎn),才能有消費者消費。生產(chǎn)者沒生產(chǎn)之前,通知消費者等待;而生產(chǎn)后通知消費者消費,消費者消費后再通知生產(chǎn)者生產(chǎn),這就是等待通知機制(Wait/Notify)。7.4多線程技術(shù)【例7-11】線程通信示例的源代碼。publicclassEx7_11_TestWaitNotify{ publicstaticvoidmain(String[]args){ ProducerThreadpt=newProducerThread(); System.out.println("生產(chǎn)結(jié)果為:sum="+pt.getSum()); }}7.4多線程技術(shù)classProducerThreadextendsThread{ longsum=0; ProducerThread(){ start(); } publicvoidrun(){ synchronized(this){ for(inti=0;i<1000;i++) sum+=i;

System.out.println("生產(chǎn)者產(chǎn)生完畢數(shù)據(jù):sum="+sum); notify(); } }//runsynchronizedpubliclonggetSum(){ try{ wait(); }catch(InterruptedExceptionex){ ex.printStackTrace(); } returnsum; }}7.4多線程技術(shù)7.4多線程技術(shù)7.4.3死鎖

死鎖是指線程間因互相等待對方的資源,而不能繼續(xù)執(zhí)行的情況。Java語言中未處理好同步問題,關(guān)鍵字synchronized使用不當就會導(dǎo)致死鎖。一般來說,持有一個共享資源的鎖并試圖獲取另一個時,就有可能發(fā)生死鎖。

造成死鎖問題的本質(zhì)是無序使用造成的,在程序設(shè)計時應(yīng)理清訪問資源的順序,確保每個線程獲取資源和釋放資源的順序正好相反。例如,假設(shè)有3個資源,獲得時順序是資源1->資源2->資源3,釋放時的順序則為資源3->資源2->資源1。7.4多線程技術(shù)7.4.4線程組

線程組(ThreadGroup)是指包含了許多線程的對象集,并可以擁有一個名字和一些相關(guān)的屬性,用于統(tǒng)一管理組中的線程。Java中,將所有線程和線程組組織在一個線程組中,形成一棵樹。若創(chuàng)建線程時不明確指定所屬的線程組,則將被放在一個默認的線程組(系統(tǒng)線程組)中。Java語言提供了一個線程組類ThreadGroup,可用于對線程組中線程和線程組進行操作,如啟動或阻塞組中的所有線程。該類的構(gòu)造方法為ThreadGroup(StringgroupName)。7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.5綜合應(yīng)用舉例

生產(chǎn)者-消費者模型是典型的多線程應(yīng)用的原型。本節(jié)介紹一個模擬生產(chǎn)者和消費者關(guān)系的程序。其中,生產(chǎn)者在一個循環(huán)中不斷生產(chǎn)了從1到10的共享數(shù)據(jù),而消費者則不斷地消費生產(chǎn)者生產(chǎn)的1到10這些共享數(shù)據(jù)?!纠?-12】生產(chǎn)者-消費者示例的源代碼。/*====================================================*文件:Ex7_12_TestProducerConsumer.java*描述:生產(chǎn)者-消費者

*包含五個類:主控類Ex7_12_TestProducerConsumer,共享數(shù)據(jù)控制類ShareData,*共享數(shù)據(jù)類MyData,生產(chǎn)者Producer和消費者Consumer*====================================================*///主控類7.5綜合應(yīng)用舉例publicclassEx7_12_TestProducerConsumer{

publicstaticvoidmain(String[]args){ ShareDatas=newShareData(); newConsumer(s).start(); newProducer(s).start(); }}//共享數(shù)據(jù)類classMyData{//可以擴展,表達復(fù)雜的數(shù)據(jù) publicintdata;}7.5綜合應(yīng)用舉例

//共享數(shù)據(jù)控制類classShareData{ //共享數(shù)據(jù) privateMyDatadata; //通知變量 privatebooleanwriteable=true;//------------------------------------------------------------------------- //需要注意的是:在調(diào)用wait()方法時,需要把它放到一個同步段里,否則將會出現(xiàn) //"java.lang.IllegalMonitorStateException:currentthreadnotowner"的異常。 //------------------------------------------------------------------------

7.5綜合應(yīng)用舉例 publicsynchronizedvoidsetShareData(MyDatadata){ if(!writeable){ try{

溫馨提示

  • 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)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論