版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第7章多線程編程
本章主要內(nèi)容:
.線程的基本概念;
.Thread類和Runnable接口;
.線程的控制;
.線程的同步;
.線程綜合示例。
2023/2/67.1線程的基本概念進(jìn)程(Process)和線程(Thread)是現(xiàn)代操作系統(tǒng)中兩個(gè)必不可少的運(yùn)行模型。操作系統(tǒng)可以運(yùn)行多個(gè)進(jìn)程,而每個(gè)一個(gè)進(jìn)程中又可以創(chuàng)建一個(gè)或多個(gè)線程。進(jìn)程通常被區(qū)分為系統(tǒng)進(jìn)程和用戶進(jìn)程,而每個(gè)Java程序可看成是一個(gè)用戶進(jìn)程,并可用Java語言提供的Thread類創(chuàng)建一個(gè)或多個(gè)線程,充分利用軟硬件資源。7.1.1線程線程是操作系統(tǒng)中重要概念之一,是程序運(yùn)行的基本執(zhí)行單元。在絕大多數(shù)平臺(tái)上,Java程序是直接利用操作系統(tǒng)的中線程來運(yùn)行的。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線程的基本概念運(yùn)行結(jié)果為:7.1線程的基本概念由上例可知,主線程(主函數(shù))和兩個(gè)線程類實(shí)例(Thread-0與Thread-1)交替輸出內(nèi)容。事實(shí)上,如果修改主線程或線程輸出內(nèi)容,各線程執(zhí)行時(shí)間也隨之變化,運(yùn)行結(jié)果也有所變化,往往是主線程和線程類實(shí)例交替輸出不定次,或者是主線程完全輸出完成后,才輪到線程類實(shí)例運(yùn)行而產(chǎn)生輸出。一般來說,創(chuàng)建線程的線程稱為父線程,而由它創(chuàng)建的線程則稱為子線程,父線程往往是主線程。7.1線程的基本概念7.1.2使用線程的優(yōu)勢充分利用CPU資源:CPU空閑時(shí),可以立刻調(diào)入另一線程;簡化編程模型:對(duì)于一些復(fù)雜的任務(wù),將各子任務(wù)由單獨(dú)的線程完成,有助于開發(fā)人員理解程序結(jié)構(gòu)和簡化開發(fā),降低維護(hù)成本;簡化異步事件的處理:不同線程可以負(fù)責(zé)處理不同的事件,不會(huì)因?yàn)槟骋皇录刺幚砘蛭刺幚硗瓿啥鵁o法響應(yīng)其他事件,有利于提高I/O應(yīng)用程序效率;7.1線程的基本概念7.1.2使用線程的優(yōu)勢使GUI程序更有效率:單線程處理GUI事件(如鼠標(biāo)單擊事件)時(shí),必須采用循環(huán)來不斷掃描隨時(shí)可能發(fā)生的GUI事件,當(dāng)某次GUI事件需要較長時(shí)間處理完時(shí),這有可能延誤及時(shí)處理后續(xù)GUI事件,而且界面會(huì)表現(xiàn)出“凍結(jié)”狀態(tài),而用多線程可以極快地處理GUI事件;節(jié)約成本:使用多線程是提高程序執(zhí)行效率的方法之一,既不需要增加硬件,又比多進(jìn)程方式更容易實(shí)現(xiàn)數(shù)據(jù)共享(在同一個(gè)進(jìn)程上下文中),是最廉價(jià)的提高程序性能的方法。7.1線程的基本概念7.1.3線程的狀態(tài)
Java語言是使用Thread類及其子類的對(duì)象來表示線程7.1線程的基本概念7.1.4線程模型進(jìn)程是正在執(zhí)行的程序。一個(gè)或多個(gè)線程構(gòu)成了一個(gè)進(jìn)程。一個(gè)線程(即執(zhí)行上下文)由三個(gè)部分組成:處理機(jī)、代碼和數(shù)據(jù)。線程的運(yùn)行過程為:占用CPU,執(zhí)行特定的程序代碼,該程序代碼操縱內(nèi)存中特定數(shù)據(jù)。在Java平臺(tái)中,類java.lang.Thread中封裝了一個(gè)虛擬處理機(jī),它控制整個(gè)線程的運(yùn)行。不同線程可以共享的方式訪問同一個(gè)公共對(duì)象,實(shí)現(xiàn)數(shù)據(jù)交換。7.1線程的基本概念多線程程序是指在一個(gè)程序內(nèi)實(shí)現(xiàn)了并發(fā)執(zhí)行的一組代碼。實(shí)際上,編程語言一般提供的是串行程序設(shè)計(jì)的方法(指順序結(jié)構(gòu)、選擇結(jié)構(gòu)和循環(huán)結(jié)構(gòu)),而計(jì)算機(jī)的并發(fā)能力由操作系統(tǒng)提供。在Java平臺(tái)中,Thread類中封裝的虛擬處理機(jī)使其在語言級(jí)提供了多線程并發(fā)的概念,通過實(shí)例化多個(gè)Thread類,可實(shí)現(xiàn)多線程編程,這為編寫多線程程序提供了極大的方便。7.1線程的基本概念Java語言中,可以通過繼承線程類Thread或?qū)崿F(xiàn)Runnable接口來創(chuàng)建用戶自定義的線程。主要內(nèi)容:
本小節(jié)主要介紹如何繼承Thread類編寫用戶自己的線程類和如何通過實(shí)現(xiàn)Runnable接口來創(chuàng)建線程。7.2創(chuàng)建線程7.2.1繼承Thread類編寫并指定線程需要執(zhí)行的方法;啟動(dòng)一個(gè)線程。線程類Thread中包含了實(shí)現(xiàn)上述功能的兩個(gè)方法:run():包含線程運(yùn)行時(shí)所執(zhí)行的代碼;start():用于啟動(dòng)線程。Java平臺(tái)中。線程類Thread是在java.lang包中定義的,但線程核心的內(nèi)容并非定義在這個(gè)類中。在這種模式下,使用線程時(shí)只需注意以下兩點(diǎn):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后還必須實(shí)例化,并用start()方法啟動(dòng)。例7-1就是按照上述流程實(shí)現(xiàn)線程編程的。Ex7_1_UnderstandThread類中包含用戶自定義的線程類MyThread和主函數(shù)main()。其中,MyThread繼承自Thread類,并覆蓋了run()方法,用戶定義了新的功能(循環(huán)輸出字符串);而主函數(shù)main()首先創(chuàng)建了MyThead實(shí)例,并用start()啟動(dòng)線程,接下來是主函數(shù)的其他功能,即循環(huán)輸出。事實(shí)上,這個(gè)程序包含兩個(gè)線程,即用戶自定義的線程和主函數(shù)線程,分別循環(huán)輸出字符串。線程類Thread除了包含run()和start()方法外,還包含一個(gè)不帶任何參數(shù)的構(gòu)造方法,因此,例7-1創(chuàng)建線程時(shí),沒帶任何參數(shù)。這使得編寫線程程序變得十分簡單。7.2創(chuàng)建線程例7-2所示的是一個(gè)典型的多線程示例。Ex7_2_TestThread繼承自Thread類,并定義了一個(gè)構(gòu)造方法Ex2_TestThread(),實(shí)現(xiàn)了傳入線程名字和輸出的功能。該類同樣覆蓋了run()方法,輸出線程名后讓線程休眠一段時(shí)間(隨機(jī)函數(shù)決定長短)。與例7-1最大的區(qū)別是,該線程類中包含主函數(shù),并且主函數(shù)創(chuàng)建了2個(gè)線程實(shí)例,先后啟動(dòng)執(zhí)行。顯然該程序一共啟動(dòng)三個(gè)線程,即主函數(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("正在運(yùn)行的線程是"+threadName); try{ Thread.sleep((int)(Math.random()*1000)); } catch(InterruptedExceptionex) { System.err.println(ex.toString()); } }//for }//runpublicstaticvoidmain(String[]args) { System.out.println("開始運(yùn)行主函數(shù)!"); Ex7_2_TestThreadthread1=newEx7_2_TestThread("如來"); Ex7_2_TestThreadthread2=newEx7_2_TestThread("孫悟空"); thread1.start(); thread2.start(); System.out.println("主函數(shù)運(yùn)行結(jié)束!"); }//main()}7.2創(chuàng)建線程開始運(yùn)行主函數(shù)!本線程的名字:如來本線程的名字:孫悟空主函數(shù)運(yùn)行結(jié)束!正在運(yùn)行的線程是如來正在運(yùn)行的線程是孫悟空正在運(yùn)行的線程是孫悟空正在運(yùn)行的線程是孫悟空正在運(yùn)行的線程是如來正在運(yùn)行的線程是如來7.2創(chuàng)建線程從上述運(yùn)行結(jié)果可以看出,主函數(shù)線程被先調(diào)入內(nèi)存,并由Java平臺(tái)啟動(dòng),因此,它先輸出結(jié)果,而后創(chuàng)建并啟動(dòng)2個(gè)線程,這時(shí)由于它還未被調(diào)出CPU,因此立刻輸出“主函數(shù)運(yùn)行結(jié)束!”,而后才是2個(gè)線程輪流執(zhí)行,輸出本線程的名字。由于系統(tǒng)為每個(gè)線程分配的時(shí)間片不固定,線程的執(zhí)行時(shí)間也不固定,因此,每次執(zhí)行程序的結(jié)果不一定相同。7.2創(chuàng)建線程7.2.2實(shí)現(xiàn)Runnable接口問題:
由于Java語言不支持多繼承,一個(gè)類如果為了使用線程而繼承自Thread類就不能再繼承其他類了,這樣很難滿足實(shí)際應(yīng)用的需要。解決:Java語言提供了接口技術(shù),通過實(shí)現(xiàn)一個(gè)或多個(gè)接口就能解決這一難題。7.2創(chuàng)建線程在java.lang包中有一個(gè)Runnable接口,通過實(shí)現(xiàn)這個(gè)接口也能實(shí)現(xiàn)線程編程。該接口只有一個(gè)抽象方法voidrun(),用于實(shí)現(xiàn)線程要執(zhí)行的代碼。實(shí)現(xiàn)Runnable接口的類并不能直接作為線程運(yùn)行,還需線程類Thread配合才能執(zhí)行。Thread類中有一個(gè)類型為Runnable的屬性,名為target。Thread類中的run()方法用到了這個(gè)屬性,并調(diào)用該屬性的run()方法,達(dá)到執(zhí)行線程的目的,即Thread類的run()方法按如下代碼實(shí)現(xiàn):7.2創(chuàng)建線程從上述代碼可以看出,如果將實(shí)現(xiàn)了Runnable接口的類的實(shí)例傳給target屬性,那么就可以通過線程類Thread的實(shí)例來達(dá)到啟動(dòng)線程的目的。事實(shí)上,Thread類提供了5個(gè)構(gòu)造函數(shù)都可以為target屬性賦值,例如可用構(gòu)造方法Thread(RunnablerunnableObject)給target屬性賦值。
由上可知,使用Runnable接口創(chuàng)建線程的步驟可以總結(jié)如下:實(shí)現(xiàn)Runnable接口,如實(shí)現(xiàn)了該接口的類為MyRunnable,并在MyRunnable類的run()方法里編寫想讓線程執(zhí)行的代碼;創(chuàng)建實(shí)現(xiàn)了Runnable接口類的實(shí)例,如創(chuàng)建MyRunnable類的實(shí)例為myRunnable;創(chuàng)建線程類Thread的實(shí)例,并用構(gòu)造方法Thread(Runnable)將myRunnable賦值給target。
經(jīng)過上述三步后,就得到了線程類實(shí)例,調(diào)用start()方法后就啟動(dòng)這個(gè)線程了。這個(gè)線程實(shí)際上是執(zhí)行MyRunnable類中的run()方法的代碼。
按照上述方法稍加修改例7-2可得到利用Runnable接口創(chuàng)建線程的完整過程。7.2創(chuàng)建線程【例7-3】實(shí)現(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("正在運(yùn)行的線程是"+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("開始運(yùn)行主函數(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ù)運(yùn)行結(jié)束!"); }//main()}7.2創(chuàng)建線程
運(yùn)行結(jié)果和例7-2完全類似,但會(huì)因MyRunnable類中run()方法執(zhí)行的時(shí)間或長點(diǎn)或短點(diǎn)(休眠的時(shí)間是隨機(jī)的)而導(dǎo)致輸出順序與例7-2的輸出順序不一致。
為了更好地理解用Runnable接口編寫多線程的應(yīng)用,例7-4展現(xiàn)了子類繼承父類時(shí)實(shí)現(xiàn)Runnable接口的情況。在該例中首先定義了一個(gè)Student類,接著定義了繼承自Student類的Master類,并實(shí)現(xiàn)了Runnable接口。7.2創(chuàng)建線程【例7-4】子類實(shí)現(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(){//覆蓋父類的方法,實(shí)現(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(){//覆蓋父類的方法,實(shí)現(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)建線程特別要注意的是,在實(shí)際應(yīng)用中,要清晰知道應(yīng)在線程類Thread的哪個(gè)子孫類中覆蓋run()方法和具體應(yīng)實(shí)現(xiàn)什么樣的功能,以免無法掌控線程的執(zhí)行。對(duì)于初學(xué)者來說,不鼓勵(lì)通過繼承Thread的派生類方式支持線程特性,而建議采用希望支持線程特性的類直接實(shí)現(xiàn)Runnable接口的方式達(dá)到這一目的(即例7-4采用的方式)7.2創(chuàng)建線程7.3深入學(xué)習(xí)Thread類
本節(jié)將繼續(xù)深入介紹Thread類的一些重要方法和屬性,包括線程的名字、優(yōu)先級(jí)以及調(diào)度等等。7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.3.2設(shè)置優(yōu)先級(jí)7.3深入學(xué)習(xí)Thread類【例7-6】優(yōu)先級(jí)設(shè)置示例的源代碼。publicclassEx7_6_ChangeThreadPriorityextendsThread{ StringthreadName; publicEx7_6_ChangeThreadPriority(StringthreadName) { System.out.println("本線程的名字:"+threadName); this.threadName=threadName; System.out.println("創(chuàng)建線程\""+this.threadName+"\"時(shí)的優(yōu)先級(jí)是"+this.getPriority()); } publicvoidrun() { System.out.println("正在運(yùn)行的線程\""+this.threadName+"\"的優(yōu)先級(jí)是"+this.getPriority()); }//run7.3深入學(xué)習(xí)Thread類
publicstaticvoidmain(String[]args) { System.out.println("開始運(yùn)行主函數(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ù)運(yùn)行結(jié)束!"); }//main()}7.3深入學(xué)習(xí)Thread類從上述結(jié)果不難看出,主函數(shù)線程的優(yōu)先級(jí)為一般,創(chuàng)建的子線程都繼承了這一優(yōu)先級(jí),即輸出為5,而在線程啟動(dòng)后,子線程的優(yōu)先級(jí)分別被調(diào)整成最低和最高。事實(shí)上,每個(gè)Java線程的優(yōu)先級(jí)都在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間,即1到10之間,而每個(gè)新線程默認(rèn)優(yōu)先級(jí)為Thread.NORM_PRIORITY。7.3深入學(xué)習(xí)Thread類為了讓優(yōu)先級(jí)較高的線程優(yōu)先執(zhí)行,系統(tǒng)按線程的優(yōu)先級(jí)調(diào)度,具有高優(yōu)先級(jí)的線程會(huì)在較低優(yōu)先級(jí)的線程之前得到執(zhí)行。如本例中,后創(chuàng)建的“孫悟空”線程因被設(shè)置成最高優(yōu)先級(jí)而優(yōu)先于先創(chuàng)建的“如來”線程執(zhí)行。多個(gè)線程運(yùn)行時(shí),線程調(diào)度是搶先式的,即如果當(dāng)前線程在執(zhí)行過程中,一個(gè)具有更高優(yōu)先級(jí)的線程進(jìn)入可執(zhí)行狀態(tài),則該高優(yōu)先級(jí)的線程會(huì)被立即調(diào)度執(zhí)行。若線程的優(yōu)先級(jí)相同,線程在就緒隊(duì)列中排隊(duì)。在分時(shí)系統(tǒng)中,每個(gè)線程按時(shí)間片輪轉(zhuǎn)方式執(zhí)行。在某些平臺(tái)上線程調(diào)度將會(huì)隨機(jī)選擇一個(gè)線程,或始終選擇第一個(gè)可以得到的線程。因此,合理設(shè)置線程的優(yōu)先級(jí)能使程序運(yùn)行更高效。7.3深入學(xué)習(xí)Thread類7.3.3線程的名字Thread類有一個(gè)類型為String的name屬性用于存儲(chǔ)線程的名字,另外有StringgetName()和voidsetName(String)兩個(gè)方法來設(shè)置或獲取這個(gè)屬性的值。另外,Thread類還提供了相應(yīng)的構(gòu)造方法,在創(chuàng)建對(duì)象時(shí)就可以指定線程的名字,具體如下:1.Thread(Stringname):接受一個(gè)String類型的變量作為線程的名字;2.Thread(Runnabletarget,Stringname):接受一個(gè)Runnable實(shí)例和一個(gè)String實(shí)例作為參數(shù)。前者的目的是傳入要執(zhí)行的代碼,即run()方法,后者則傳入線程名字。
如果在創(chuàng)建線程是未指定名字,默認(rèn)名字一般是“Thread-”加上一個(gè)遞增的整數(shù);對(duì)于主線程來說,它的默認(rèn)名字一般會(huì)被設(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("這個(gè)線程的名字是:"+this.getName()); }}//classShowThreadName7.3深入學(xué)習(xí)Thread類
運(yùn)行結(jié)果為:
這個(gè)線程的名字是:如來
這個(gè)線程的名字是:Thread-0
上述程序啟動(dòng)2個(gè)線程,第一個(gè)是輸出默認(rèn)線程名,第二個(gè)是輸出用戶設(shè)定的線程名。需要注意的是兩個(gè)線程輸出結(jié)果與語句順序相反。7.3深入學(xué)習(xí)Thread類7.3.4得到當(dāng)前線程
Java代碼是由某一Java線程執(zhí)行的,Thread類提供一個(gè)靜態(tài)方法currentThread()用于獲得這一線程,以便進(jìn)一步控制線程的執(zhí)行。該方法的返回值是Thread的引用,這個(gè)引用所指向的Thread類的實(shí)例正是“執(zhí)行當(dāng)前代碼的線程”。7.3深入學(xué)習(xí)Thread類【例7-8】當(dāng)前線程示例的源代碼。publicclassEx7_8_CurrentThreadName{ publicstaticvoidmain(String[]args){ Threadthread=Thread.currentThread(); System.out.println("當(dāng)前線程的名字是:"+thread.getName()); ShowCurrentThreadNamecthread=newShowCurrentThreadName(); cthread.start(); }}//classEx7_8_CurrentThreadName7.3深入學(xué)習(xí)Thread類classShowCurrentThreadNameextendsThread{ publicvoidrun(){ System.out.println("這個(gè)線程的名字是:"+this.getName()); Threadthread=Thread.currentThread(); System.out.println("當(dāng)前線程的名字是:"+thread.getName()); }}//classShowCurrentThreadName7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類上述程序首先調(diào)用CurrentThreadName()方法得到主線程的引用,而后調(diào)用getName()方法輸出名字,而后創(chuàng)建ShowCurrentThreadName類的實(shí)例并啟動(dòng),該類繼承自Thread,在run()方法中調(diào)用CurrentThreadName()方法得到該線程的名字并輸出。上述示例雖然演示了得到當(dāng)前線程的方法如何使用,但并未講清這一方法的具體應(yīng)用場合和作用,目前至少還存在這么一個(gè)疑問:自己寫的程序難道不知道正被哪個(gè)線程執(zhí)行嗎?何必多此一舉用這個(gè)方法確定呢?事實(shí)上,多線程編程時(shí),如果要?jiǎng)?chuàng)建一組協(xié)調(diào)工作的線程,而這些線程中的一部分又由同一線程類的派生類創(chuàng)建而成,那么同時(shí)工作的若干個(gè)線程運(yùn)行代碼是相同的,即會(huì)執(zhí)行同一個(gè)run()方法。若為了使某一特定線程執(zhí)行一些特定的任務(wù),則可在run()方法中得到當(dāng)前線程,進(jìn)而加以控制。7.3深入學(xué)習(xí)Thread類7.3.5線程的休眠Thread類還有另外一個(gè)靜態(tài)方法sleep(),可用于讓線程沉睡若干毫秒。它沒有返回值,只接受一個(gè)long類型的參數(shù),這個(gè)參數(shù)就是傳入讓線程沉睡的毫秒數(shù)。例如,參數(shù)為1000時(shí),sleep()方法的執(zhí)行結(jié)果就是讓當(dāng)前線程沉睡1秒鐘,而1秒后,線程會(huì)自動(dòng)蘇醒,并繼續(xù)執(zhí)行后續(xù)的代碼。當(dāng)然這里所說的“沉睡”就是前面介紹的線程狀態(tài)之一,即線程會(huì)轉(zhuǎn)入“被掛起”或者“掛起”狀態(tài),而當(dāng)沉睡時(shí)間到時(shí),線程又會(huì)被轉(zhuǎn)為“運(yùn)行”狀態(tài)。顯然,并不是嚴(yán)格地沉睡1秒鐘,實(shí)際上可能被掛起1.001秒或更長點(diǎn),而這對(duì)一般的應(yīng)用程序來說,時(shí)間控制的精度足夠了,需要注意的是在高速數(shù)據(jù)采集等時(shí)間精度要求極高的場合,需選用其他方法確保時(shí)間的精度。7.3深入學(xué)習(xí)Thread類
sleep()方法并非一定能夠運(yùn)行成功,當(dāng)線程處在掛起狀態(tài)時(shí),由于某種原因被打斷了,它會(huì)拋出一個(gè)類型為InterruptedException的異常。例如,以10000為參數(shù)執(zhí)行sleep()方法時(shí),線程應(yīng)該掛起10秒鐘左右,但是當(dāng)線程被掛起8秒鐘以后,因某種原因被打斷了,那么就會(huì)拋出這個(gè)異常,即該線程只沉睡了約8秒鐘。如果沒特殊的需求,這個(gè)異常沒必要向外傳遞,用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()方法可以讓當(dāng)前線程沉睡(休眠或掛起)外,Thread類的其他方法也可以用于控制線程的狀態(tài)。Yield()方法也是一個(gè)靜態(tài)方法,可以用來使具有相同優(yōu)先級(jí)的線程獲得執(zhí)行機(jī)會(huì),即它會(huì)暫停當(dāng)前的線程,并將其放入可運(yùn)行隊(duì)列,而選同優(yōu)先級(jí)的另一線程運(yùn)行。當(dāng)然,如果沒有相同優(yōu)先級(jí)的可運(yùn)行線程,yield()將什么也不做,繼續(xù)讓當(dāng)前線程執(zhí)行。需要注意的是:sleep()調(diào)用會(huì)給較低優(yōu)先級(jí)的線程一個(gè)運(yùn)行機(jī)會(huì),而yield()方法只會(huì)給相同優(yōu)先級(jí)線程一個(gè)執(zhí)行機(jī)會(huì)。7.3深入學(xué)習(xí)Thread類Thread類提供的join()方法也是用于控制線程的,它的特別之處在于應(yīng)用的場合特殊。如果某線程(線程A)只有在另一線程(線程B)終止時(shí)才能繼續(xù)執(zhí)行,則這個(gè)線程(線程A)可以調(diào)用另一線程(線程B)的join()方法,將兩個(gè)線程“聯(lián)結(jié)”在一起,即線程A先執(zhí)行,而后被掛起,線程B執(zhí)行,直到終止時(shí)線程A回到可運(yùn)行狀態(tài)繼續(xù)執(zhí)行。另外,join(inttime)方法可以傳入一個(gè)最多等待時(shí)間的參數(shù),用于控制等待時(shí)間。7.3深入學(xué)習(xí)Thread類【例7-9】線程聯(lián)結(jié)示例的源代碼。publicclassEx7_9_UseJoin{ publicstaticvoidmain(String[]args){ System.out.println("主線程啟動(dòng)執(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ùn)行,預(yù)計(jì)執(zhí)行3秒!"); try{ Thread.sleep(3*1000); }catch(InterruptedExceptionex){ System.err.println(ex.toString()); } System.out.println("子線程準(zhǔn)備運(yùn)行完畢退出!"); }}7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.3深入學(xué)習(xí)Thread類7.4多線程技術(shù)
多線程編程涉及到線程的同步、通訊和死鎖等,Java語言除了提供相關(guān)技術(shù)外,還提供了線程組的概念。本小節(jié)將詳細(xì)介紹這些技術(shù),為實(shí)現(xiàn)高效可靠的多線程程序提供支持。7.4.1線程同步
由于同一進(jìn)程的多個(gè)線程有時(shí)需要共享一個(gè)對(duì)象,若它們同時(shí)訪問該對(duì)象,必然會(huì)產(chǎn)生訪問共享數(shù)據(jù)的沖突。例如,某一個(gè)線程在更新該對(duì)象的同時(shí),而另一個(gè)線程也試圖更新或讀取該對(duì)象,這樣將破壞數(shù)據(jù)的一致性。為避免多個(gè)線程同時(shí)訪問一個(gè)共享對(duì)象帶來的訪問沖突問題,Java語言提供了專門的機(jī)制來解決,即線程同步。
線程同步可以有效控制多個(gè)線程爭搶訪問同一對(duì)象的問題,從而避免一個(gè)線程剛生成的數(shù)據(jù)又會(huì)被其他線程生成的數(shù)據(jù)覆蓋等問題。Java語言是用監(jiān)聽器手段來達(dá)到這一目的的。監(jiān)聽器為受保護(hù)的資源(共享對(duì)象)加一個(gè)“訪問鎖”和配一把“鑰匙”,每一要訪問該資源的線程必須先申請(qǐng)“鑰匙”,當(dāng)?shù)玫借€匙后才能對(duì)受保護(hù)的資源執(zhí)行操作,而其他線程只能等待,直到它們拿到這把鑰匙。7.4多線程技術(shù)Java語言提供了關(guān)鍵字synchronized來實(shí)現(xiàn)多個(gè)線程的同步,并區(qū)分為兩種實(shí)現(xiàn)方法:一種是方法同步,另一種是對(duì)象同步。
方法同步是為了防止多線程訪問同一方法導(dǎo)致數(shù)據(jù)崩潰。具體來說,在定義方法時(shí)加上關(guān)鍵字synchronized修飾即可,這能保證某一線程在其他任何線程訪問這一方法前完成一次執(zhí)行,即某一線程一旦啟動(dòng)對(duì)該方法的訪問,其他線程只能等待這個(gè)線程執(zhí)行完這個(gè)方法后再訪問。定義synchronized方法的格式如下:7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)
事實(shí)上,對(duì)于一個(gè)需要較長時(shí)間執(zhí)行的方法來說,其中訪問關(guān)鍵數(shù)據(jù)的時(shí)間可能很短,如果將整個(gè)方法申明為synchronized,將導(dǎo)致其他線程因無法調(diào)用該方法而長時(shí)間無法得到執(zhí)行,這不利于提高程序的運(yùn)行效率。這時(shí),就可以使用對(duì)象同步,只把訪問關(guān)鍵數(shù)據(jù)的代碼段用花括號(hào)括起來,在其前面加上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ù)//必須有這段代碼,否則有可能主線程未等到子線程運(yùn)行完就輸出結(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)用中,需要線程之間互相交流和等待,實(shí)現(xiàn)互相通信。具體來說,可以通過共享的數(shù)據(jù)做到線程互相交流,通過線程控制方法使線程互相等待。Java語言的java.lang.Object類提供了wait()、notify()和notifyAll()等三個(gè)方法來協(xié)調(diào)線程間的運(yùn)行進(jìn)度關(guān)系,實(shí)現(xiàn)線程通信的。
線程通信是建立在生產(chǎn)者和消費(fèi)者模型之上的,即一個(gè)(組)線程產(chǎn)生輸出(相當(dāng)于生產(chǎn)產(chǎn)品,該線程稱為生產(chǎn)者,產(chǎn)生一串?dāng)?shù)據(jù)流),另一(組)線程進(jìn)行輸入(相當(dāng)于消費(fèi)者,該線程稱為消費(fèi)者,消耗數(shù)據(jù)流中的數(shù)據(jù)),先有生產(chǎn)者生產(chǎn),才能有消費(fèi)者消費(fèi)。生產(chǎn)者沒生產(chǎn)之前,通知消費(fèi)者等待;而生產(chǎn)后通知消費(fèi)者消費(fèi),消費(fèi)者消費(fèi)后再通知生產(chǎn)者生產(chǎn),這就是等待通知機(jī)制(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死鎖
死鎖是指線程間因互相等待對(duì)方的資源,而不能繼續(xù)執(zhí)行的情況。Java語言中未處理好同步問題,關(guān)鍵字synchronized使用不當(dāng)就會(huì)導(dǎo)致死鎖。一般來說,持有一個(gè)共享資源的鎖并試圖獲取另一個(gè)時(shí),就有可能發(fā)生死鎖。
造成死鎖問題的本質(zhì)是無序使用造成的,在程序設(shè)計(jì)時(shí)應(yīng)理清訪問資源的順序,確保每個(gè)線程獲取資源和釋放資源的順序正好相反。例如,假設(shè)有3個(gè)資源,獲得時(shí)順序是資源1->資源2->資源3,釋放時(shí)的順序則為資源3->資源2->資源1。7.4多線程技術(shù)7.4.4線程組
線程組(ThreadGroup)是指包含了許多線程的對(duì)象集,并可以擁有一個(gè)名字和一些相關(guān)的屬性,用于統(tǒng)一管理組中的線程。Java中,將所有線程和線程組組織在一個(gè)線程組中,形成一棵樹。若創(chuàng)建線程時(shí)不明確指定所屬的線程組,則將被放在一個(gè)默認(rèn)的線程組(系統(tǒng)線程組)中。Java語言提供了一個(gè)線程組類ThreadGroup,可用于對(duì)線程組中線程和線程組進(jìn)行操作,如啟動(dòng)或阻塞組中的所有線程。該類的構(gòu)造方法為ThreadGroup(StringgroupName)。7.4多線程技術(shù)7.4多線程技術(shù)7.4多線程技術(shù)7.5綜合應(yīng)用舉例
生產(chǎn)者-消費(fèi)者模型是典型的多線程應(yīng)用的原型。本節(jié)介紹一個(gè)模擬生產(chǎn)者和消費(fèi)者關(guān)系的程序。其中,生產(chǎn)者在一個(gè)循環(huán)中不斷生產(chǎn)了從1到10的共享數(shù)據(jù),而消費(fèi)者則不斷地消費(fèi)生產(chǎn)者生產(chǎn)的1到10這些共享數(shù)據(jù)?!纠?-12】生產(chǎn)者-消費(fèi)者示例的源代碼。/*====================================================*文件:Ex7_12_TestProducerConsumer.java*描述:生產(chǎn)者-消費(fèi)者
*包含五個(gè)類:主控類Ex7_12_TestProducerConsumer,共享數(shù)據(jù)控制類ShareData,*共享數(shù)據(jù)類MyData,生產(chǎn)者Producer和消費(fèi)者Consumer*====================================================*///主控類7.5綜合應(yīng)用舉例publicclassEx7_12_TestProducerConsumer{
publicstaticvoidmain(String[]args){ ShareDatas=newShareData(); newConsumer(s).start(); newProducer(s).start(); }}//共享數(shù)據(jù)類classMyData{//可以擴(kuò)展,表達(dá)復(fù)雜的數(shù)據(jù) publicintdata;}7.5綜合應(yīng)用舉例
//共享數(shù)據(jù)控制類classShareData{ //共享數(shù)據(jù) privateMyDatadata; //通知變量 privatebooleanwriteable=true;//------------------------------------------------------------------------- //需要注意的是:在調(diào)用wait()方法時(shí),需要把它放到一個(gè)同步段里,否則將會(huì)出現(xiàn) //"java.lang.IllegalMonitorStateException:currentthreadnotowner"的異常。 //------------------------------------------------------------------------
7.5綜合應(yīng)用舉例 publicsynchronizedvoidsetShareData(MyDatadata){ if(!writeable){ try{
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 臨時(shí)店鋪?zhàn)赓U合同
- 工礦加工合同模板
- 簡單建材供貨合同
- 普通員工勞動(dòng)合同的示范文本
- 手機(jī)美容保護(hù)膜系統(tǒng)購銷協(xié)議范本
- 工程合同管理費(fèi)用解析
- 商場裝修合同書
- 機(jī)構(gòu)內(nèi)訓(xùn)合作協(xié)議書樣本
- 工程項(xiàng)目機(jī)械交易合同樣本
- 2024年委托貸款發(fā)放協(xié)議
- 2024年國企改革培訓(xùn)
- 特種設(shè)備“日管控”安全檢查記錄、每周安全排查治理報(bào)告
- 2023年江蘇南京航空航天大學(xué)工作人員招聘56人筆試《行政職業(yè)能力測驗(yàn)》模擬試卷(答案詳解版)
- 2024年中國中煤能源集團(tuán)限公司招聘10人高頻考題難、易錯(cuò)點(diǎn)模擬試題(共500題)附帶答案詳解
- 心理健康科普文化墻
- 【川教版】《生態(tài) 生命 安全》四年級(jí)上冊第10課《認(rèn)識(shí)傳染病》課件
- 家庭影音室裝修方案
- 2024年浙江杭州濱江城建發(fā)展有限公司招聘筆試參考題庫含答案解析
- 槍庫應(yīng)急處置預(yù)案
- 處理不同類型客戶的技巧與策略
- 2024年給藥錯(cuò)誤護(hù)理不良事件分析持續(xù)改進(jìn)
評(píng)論
0/150
提交評(píng)論