版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
本模塊通過3個(gè)任務(wù)主要介紹線程的概念、線程的創(chuàng)建、線程的生命周期及狀態(tài)轉(zhuǎn)換、線程的調(diào)度、多線程同步以及線程通信。模塊介紹思維導(dǎo)圖教學(xué)大綱能力目標(biāo)◎會(huì)使用多種方式創(chuàng)建線程◎會(huì)使用線程同步優(yōu)化程序◎會(huì)使用多線程通信完成要求功能知識(shí)目標(biāo)◎了解多線程的概念◎掌握多線程創(chuàng)建的方式◎理解線程的生命周期及狀態(tài)轉(zhuǎn)換◎掌握線程的調(diào)度◎掌握多線程同步◎掌握多線程通信教學(xué)大綱學(xué)習(xí)重點(diǎn)◎多線程的創(chuàng)建◎線程的同步學(xué)習(xí)難點(diǎn)◎多線程通信任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)目標(biāo)了解多線程的概念掌握多線程創(chuàng)建的方式任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)描述要求程序?qū)崿F(xiàn)開啟多個(gè)窗口售票,運(yùn)行效果如下圖所示:圖8-1任務(wù)1運(yùn)行效果圖任務(wù)8.1多窗口售票程序設(shè)計(jì)知識(shí)準(zhǔn)備8.1.1線程概述
前面所有的案例程序都是從main()方法入口開始執(zhí)行到程序結(jié)束,整個(gè)過程只能順序執(zhí)行,如果程序在某個(gè)地方出現(xiàn)問題,則程序就會(huì)崩潰。這種程序因?yàn)槭菃尉€程的,比較脆弱和局限,如果用來實(shí)現(xiàn)售票程序,則相當(dāng)于只能開啟一個(gè)售票窗口進(jìn)行售票。如果我們需要開啟多個(gè)窗口售票,則需要使用多線程技術(shù)。
在程序設(shè)計(jì)中,多線程就是指一個(gè)應(yīng)用程序中有多條并發(fā)執(zhí)行的線程,它們交替執(zhí)行,且彼此間可以通信。任務(wù)8.1多窗口售票程序設(shè)計(jì)圖7-2I/O流的頂層類1.進(jìn)程的概念
在操作系統(tǒng)中,每個(gè)獨(dú)立執(zhí)行的程序都可稱之為一個(gè)進(jìn)程。例如同時(shí)運(yùn)行微信、QQ、音樂播放軟件等。
在多任務(wù)操作系統(tǒng)中,我們可以一邊聽音樂,一邊處理郵件,但實(shí)際上這些進(jìn)程并不是在同一時(shí)刻運(yùn)行的。因?yàn)橥粫r(shí)刻CPU只能運(yùn)行一個(gè)進(jìn)程,是操作系統(tǒng)為每一個(gè)進(jìn)程分配一段有限的CPU使用時(shí)間片段,每個(gè)CPU時(shí)間片段執(zhí)行一個(gè)進(jìn)程,由于CPU速度很快,可以在極短的時(shí)間內(nèi)在不同進(jìn)程間切換,給我們的感覺就是多個(gè)程序同時(shí)運(yùn)行。任務(wù)8.1多窗口售票程序設(shè)計(jì)圖7-2I/O流的頂層類2.線程的概念
在多任務(wù)操作系統(tǒng)中,在一個(gè)進(jìn)程中可以有多個(gè)執(zhí)行單元同時(shí)運(yùn)行,來完成程序任務(wù),這些并行執(zhí)行的單元就是線程。每個(gè)進(jìn)程至少存在一個(gè)線程,但Java程序啟動(dòng)時(shí),就會(huì)產(chǎn)生一個(gè)進(jìn)程,進(jìn)程中會(huì)默認(rèn)創(chuàng)建一個(gè)線程,在這個(gè)線程中會(huì)運(yùn)行main()方法中的代碼。
如果希望程序中實(shí)現(xiàn)多段程序代碼交替運(yùn)行,則需要?jiǎng)?chuàng)建多個(gè)線程,即多線程程序。多線程程序在運(yùn)行時(shí),每個(gè)線程之間都是獨(dú)立的,可以并發(fā)執(zhí)行,可以互相通信。表面上看多線程是并發(fā)執(zhí)行的,實(shí)際上它們和進(jìn)程一樣,也是輪流使用CPU時(shí)間片段執(zhí)行的,只是因?yàn)镃PU速度很快,且每個(gè)時(shí)間片段極短,給我們的感覺是多線程并行執(zhí)行。任務(wù)8.1多窗口售票程序設(shè)計(jì)8.1.2線程的創(chuàng)建在Java語言中,可以使用三種方式來實(shí)現(xiàn)多線程:繼承Thread類實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)Callable接口任務(wù)8.1多窗口售票程序設(shè)計(jì)Thread類實(shí)現(xiàn)多線程說明:在java.lang包下有一個(gè)線程類Thread,可以通過繼承Thread類來實(shí)現(xiàn)多線程。步驟:1)首先需要先創(chuàng)建一個(gè)自定義的子線程類,繼承自Thread類,同時(shí)需要重寫Thread類的run()方法。2)然后創(chuàng)建子線程類的實(shí)例對(duì)象;3)并調(diào)用類的start()方法啟動(dòng)子線程。案例:例8-1Example8_1.java任務(wù)8.1多窗口售票程序設(shè)計(jì)Thread類實(shí)現(xiàn)多線程案例:例8-1Example8_1.java//自定義一個(gè)子線程類,繼承自Thread類
classMyThreadextendsThread{
//構(gòu)造方法,初始化子線程名
publicMyThread(Stringname){
super(name);
}
//重寫run()方法,方法內(nèi)是子線程要執(zhí)行的語句
publicvoidrun(){
for(inti=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":子線程在運(yùn)行");
}
}
}
任務(wù)8.1多窗口售票程序設(shè)計(jì)2.Runnable接口實(shí)現(xiàn)多線程說明:Java只支持類的單繼承,一旦某個(gè)類已經(jīng)繼承了其他父類,則無法再繼承Thread類來實(shí)現(xiàn)多線程。這種情況下,就可以通過實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程。步驟:1)創(chuàng)建Runnable接口的實(shí)現(xiàn)類,重寫接口的run()方法;2)創(chuàng)建Runnable接口的實(shí)現(xiàn)類對(duì)象;3)創(chuàng)建線程實(shí)例;4)調(diào)用start()方法啟動(dòng)線程。案例:例8-2Example8_2.java任務(wù)8.1多窗口售票程序設(shè)計(jì)2.Runnable接口實(shí)現(xiàn)多線程案例:例8-2Example8_2.java//定義Runnable接口的實(shí)現(xiàn)類classMyRunnableimplementsRunnable{//重寫接口的run()方法@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":子線程在運(yùn)行");}}}任務(wù)8.1多窗口售票程序設(shè)計(jì)3.Callable接口實(shí)現(xiàn)多線程說明:通過實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程時(shí),由于run()方法沒有返回值,因此無法從子線程獲得返回結(jié)果。從JDK5開始,Java提供了Callable接口用來實(shí)現(xiàn)多線程,該實(shí)現(xiàn)可以從子線程獲取返回值的需求。使用Callable接口實(shí)現(xiàn)多線程需要用到類FutureTask,構(gòu)造線程對(duì)象的時(shí)候傳遞的是FutureTask類對(duì)象而不再是接口的實(shí)現(xiàn)類對(duì)象。步驟:1)創(chuàng)建Callable接口的實(shí)現(xiàn)類,重寫接口的call()方法;2)創(chuàng)建Callable接口的實(shí)現(xiàn)類對(duì)象;3)使用類FutureTask先封裝Callable接口實(shí)現(xiàn)類對(duì)象得到FutureTask類對(duì)象;4)使用Tread類創(chuàng)建線程實(shí)例,傳遞參數(shù)為FutureTask類對(duì)象;5)調(diào)用start()方法啟動(dòng)線程。任務(wù)8.1多窗口售票程序設(shè)計(jì)3.Callable接口實(shí)現(xiàn)多線程案例:例8-3Example8_3.java//定義Callable接口的實(shí)現(xiàn)類classMyCallableimplementsCallable{//重寫重寫接口的call()方法@OverridepublicObjectcall()throwsException{inti=0;for(;i<3;i++){System.out.println(Thread.currentThread().getName()+
":子線程call()方法在運(yùn)行");}returni;}}任務(wù)實(shí)施根據(jù)任務(wù)分析可知:(1)售票廳有3個(gè)窗口可發(fā)售某日某次車的50張車票;(2)這50張車票可以看做共享資源;(3)3個(gè)售票窗口相當(dāng)于3個(gè)線程。由于沒有要求售票子線程返回結(jié)果,故既可以使用前面學(xué)習(xí)的類Thread來實(shí)現(xiàn)多窗口售票程序,也可以使用接口Runnable來實(shí)現(xiàn)多窗口售票程序。任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)實(shí)現(xiàn)方法1:繼承類Thread來實(shí)現(xiàn)多窗口售票程序Auto_Ticketing.java//自定義一個(gè)子線程類,繼承自Thread類classTicketWindowextendsThread{privateinttickets=50;//構(gòu)造方法,初始化子線程名publicTicketWindow(Stringname){super(name);}@Overridepublicvoidrun(){//重寫方法run()while(tickets>0){System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}}}publicclassAuto_Ticketing{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow的實(shí)例對(duì)象1TicketWindowticketWindow1=newTicketWindow("售票窗口1");ticketWindow1.start();//啟動(dòng)線程//創(chuàng)建子線程類TicketWindow的實(shí)例對(duì)象2TicketWindowticketWindow2=newTicketWindow("售票窗口2");ticketWindow2.start();//啟動(dòng)線程//創(chuàng)建子線程類TicketWindow的實(shí)例對(duì)象3TicketWindowticketWindow3=newTicketWindow("售票窗口3");ticketWindow3.start();//啟動(dòng)線程}}存在問題:每個(gè)窗口各自銷售50張票,運(yùn)行結(jié)果如下圖所示:任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)實(shí)現(xiàn)方法2:實(shí)現(xiàn)接口Runnable來實(shí)現(xiàn)多窗口售票程序Auto_Ticketing2.javaclassTicketWindow_RunableimplementsRunnable{privateinttickets=50;//實(shí)現(xiàn)接口方法run()publicvoidrun(){while(tickets>0){System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}}}
publicclassAuto_Ticketing2{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow_Runable的實(shí)例對(duì)象TicketWindow_RunableticketWindow=newTicketWindow_Runable();//創(chuàng)建三個(gè)子線程用于售票newThread(ticketWindow,"售票窗口1").start();newThread(ticketWindow,"售票窗口2").start();newThread(ticketWindow,"售票窗口3").start();}}任務(wù)8.1多窗口售票程序設(shè)計(jì)兩種任務(wù)實(shí)現(xiàn)方式對(duì)比:
通過以上兩行不同的實(shí)現(xiàn)售票程序的方式,可以看到通過實(shí)現(xiàn)Runnable接口方式相對(duì)于繼承Thread方式實(shí)現(xiàn)多線程來說有一定的優(yōu)勢(shì):(1)接口方式可以避免類的單繼承帶來的局限性;(2)接口方式更適合多個(gè)子線程處理共享資源的情況。實(shí)踐訓(xùn)練按要求完成如下程序。(1)實(shí)現(xiàn)三個(gè)同學(xué)分吃10塊蛋糕的應(yīng)用;
(2)顯示分吃過程,例如:“同學(xué)A分吃了蛋糕10”“同學(xué)B分吃了蛋糕9”等。任務(wù)8.1多窗口售票程序設(shè)計(jì)任務(wù)8.2優(yōu)化多窗口售票程序任務(wù)2目標(biāo)了解線程的生命周期及狀態(tài)轉(zhuǎn)換掌握線程的調(diào)度掌握多線程同步任務(wù)8.2優(yōu)化多窗口售票程序任務(wù)2描述
在實(shí)際多窗口售票過程中,每個(gè)窗口售票都需要耗費(fèi)一定的時(shí)間,所以每個(gè)售票窗口的線程是有延遲的。任務(wù)需要模擬線程的延遲,并確保在線程有延遲的情況下,售票可以正常進(jìn)行,所售總票數(shù)修改為20張,運(yùn)行效果如圖8-7所示。圖8-7任務(wù)2運(yùn)行效果圖任務(wù)8.2優(yōu)化多窗口售票程序知識(shí)準(zhǔn)備8.2.1概述線程的生命周期及狀態(tài)轉(zhuǎn)換Java線程的生命周期中包含6種狀態(tài),這六種狀態(tài)存放在Thread類的內(nèi)部類State中,線程狀態(tài)之間的轉(zhuǎn)換可以看圖8-8所示。圖8-8線程狀態(tài)轉(zhuǎn)換圖任務(wù)8.2優(yōu)化多窗口售票程序8.2.1概述線程的生命周期及狀態(tài)轉(zhuǎn)換下面對(duì)線程的6種狀態(tài)做詳細(xì)講解。新建狀態(tài)(NEW):當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個(gè)線程對(duì)象后,該線程就處于新建狀態(tài),此時(shí)JVM會(huì)為其分配內(nèi)存,并初始化對(duì)象的成員變量??蛇\(yùn)行狀態(tài)(RUNNABLE):當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程就進(jìn)入可運(yùn)行狀態(tài)。Java虛擬機(jī)會(huì)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,等待調(diào)度運(yùn)行??蛇\(yùn)行狀態(tài)又細(xì)分為兩種狀態(tài):就緒狀態(tài)(READY)和運(yùn)行狀態(tài)(RUNNING),線程根據(jù)能否獲得CPU執(zhí)行時(shí)間在就緒和運(yùn)行狀態(tài)間轉(zhuǎn)換。就緒狀態(tài):線程對(duì)象調(diào)用了start()方法后,該線程并沒有立刻運(yùn)行,要等待JVM(Java虛擬機(jī))的調(diào)度,此時(shí)就是就緒狀態(tài),相當(dāng)于“等待執(zhí)行”。運(yùn)行狀態(tài):當(dāng)線程對(duì)象獲得JVM調(diào)度,也就是獲得CPU使用權(quán)后即轉(zhuǎn)換為運(yùn)行狀態(tài),開始執(zhí)行run()方法或call()方法的線程執(zhí)行體。任務(wù)8.2優(yōu)化多窗口售票程序8.2.1概述線程的生命周期及狀態(tài)轉(zhuǎn)換阻塞狀態(tài)(BLOCKED):當(dāng)處于運(yùn)行狀態(tài)的線程失去所占用資源之后,便停止運(yùn)行進(jìn)入阻塞狀態(tài)。阻塞狀態(tài)的線程只有先進(jìn)入就緒狀態(tài),才能有機(jī)會(huì)轉(zhuǎn)換進(jìn)入運(yùn)行狀態(tài)。線程進(jìn)入阻塞狀態(tài)通常是由兩種原因產(chǎn)生的:線程運(yùn)行過程中,獲取同步鎖失??;線程運(yùn)行過程中,發(fā)出I/O請(qǐng)求時(shí);無限期等待狀態(tài)(WAITING):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待被其他線程顯式地喚醒。以下方法會(huì)讓線程陷入無限期的等待狀態(tài):沒有設(shè)置Timeout參數(shù)的Object.wait()方法沒有設(shè)置Timeout參數(shù)的Object.join()方法任務(wù)8.2優(yōu)化多窗口售票程序8.2.1概述線程的生命周期及狀態(tài)轉(zhuǎn)換限期等待狀態(tài)(TIMED_WAITING):處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過無須等待其他線程顯式地喚醒,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒并進(jìn)入可運(yùn)行時(shí)狀態(tài)的就緒狀態(tài)。以下方法會(huì)讓線程進(jìn)入限期等待狀態(tài):設(shè)置了Timeout參數(shù)的Object.wait(longmillis)方法設(shè)置了Timeout參數(shù)的Object.sleep(longmillis)方法設(shè)置了Timeout參數(shù)的Object.join(longmillis)方法終止?fàn)顟B(tài)(TERMINATED):線程執(zhí)行完了或者因異常退出了run()方法或call()方法,該線程就進(jìn)入終止?fàn)顟B(tài),生命周期結(jié)束。任務(wù)8.2優(yōu)化多窗口售票程序8.2.2線程的調(diào)度
程序中的多個(gè)線程看起來是并發(fā)執(zhí)行的,但實(shí)際上并一定是同一時(shí)刻執(zhí)行。線程要執(zhí)行,必須先獲得CPU使用權(quán),Java虛擬機(jī)會(huì)按照特定的機(jī)制為程序中的線程分配CPU的使用權(quán),這種機(jī)制稱為線程的調(diào)度。
線程調(diào)度有兩種模型:分時(shí)調(diào)度模型:讓所有的線程輪流獲得CPU的使用權(quán),并且平均分配每個(gè)線程占用的CPU時(shí)間片。搶占式調(diào)度模型:讓可運(yùn)行池中所有就緒狀態(tài)的線程爭(zhēng)搶CPU的使用權(quán),高優(yōu)先級(jí)的線程獲取CPU使用權(quán)的概率大于低優(yōu)先級(jí)的線程。下面圍繞線程調(diào)度進(jìn)行詳細(xì)的講解。任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.1線程的優(yōu)先級(jí)
如果要對(duì)線程進(jìn)行調(diào)度,最直接的方式是設(shè)置線程的優(yōu)先級(jí)。優(yōu)先級(jí)高的線程獲得CPU使用權(quán)的機(jī)會(huì)大于優(yōu)先級(jí)低的線程。
在Java中設(shè)置線程的優(yōu)先級(jí)可使用Thread類的方法setPriority(intnewPriority),線程的優(yōu)先級(jí)可以設(shè)置為1—10之間的整數(shù),數(shù)字越大代表優(yōu)先級(jí)越高;除了使用數(shù)字,Thread類還提供了三個(gè)優(yōu)先級(jí)靜態(tài)常量表示線程的優(yōu)先級(jí):MAX_PRIORITY=10,最高優(yōu)先級(jí)MIN_PRIORITY=1,最低優(yōu)先級(jí)NORM_PRIORITY=5,默認(rèn)優(yōu)先級(jí)
如果沒有設(shè)置線程的優(yōu)先級(jí),則線程具有默認(rèn)優(yōu)先級(jí),主線程默認(rèn)優(yōu)先級(jí)為5,如果A線程創(chuàng)建了B線程,那么B線程和A線程具有相同的優(yōu)先級(jí)。任務(wù)8.2優(yōu)化多窗口售票程序下面我們通過一個(gè)案例來查看不同優(yōu)先級(jí)的線程執(zhí)行情況,如例8-4Example8_4.java所示。publicclassExample8_4{publicstaticvoidmain(String[]args){//分別創(chuàng)建兩個(gè)線程對(duì)象,用于循環(huán)輸出語句Threadthread1=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":abcd");}}},"低優(yōu)先級(jí)的線程");Threadthread2=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":1234");}}},"高優(yōu)先級(jí)的線程");thread1.setPriority(Thread.MIN_PRIORITY);//設(shè)置線程優(yōu)先級(jí)為最低級(jí)別thread2.setPriority(Thread.MAX_PRIORITY);//設(shè)置線程優(yōu)先級(jí)為最高級(jí)別//分別開啟兩個(gè)線程thread1.start();thread2.start();}}運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.1線程的優(yōu)先級(jí)
注意:
線程的優(yōu)先級(jí)需要操作系統(tǒng)的支持,不同的操作系統(tǒng)支持的線程優(yōu)先級(jí)不同的,不一定能和Java中支持的線程優(yōu)先級(jí)對(duì)應(yīng)。建議使用時(shí)使用前面講述的三個(gè)常量?jī)?yōu)先級(jí)。另外,設(shè)計(jì)多線程應(yīng)用時(shí),不能只依賴于線程優(yōu)先級(jí)來實(shí)現(xiàn)所需功能。任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.2線程的休眠
如果在線程執(zhí)行過程中,要暫停正在執(zhí)行的線程,讓出CPU使用權(quán),可以使用方法staticvoidsleep(longmillis)來設(shè)置讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入休眠等待狀態(tài),這樣其他的線程可以得到執(zhí)行的機(jī)會(huì)。sleep()方法會(huì)聲明拋出InterruptedException異常,故在調(diào)用該方法時(shí)應(yīng)捕獲處理該異常或者是聲明拋出該異常。下面我們通過一個(gè)案例來查看sleep()方法的使用情況,如例:8-5所示。例8-5Example8_5.java任務(wù)8.2優(yōu)化多窗口售票程序publicclassExample8_5{publicstaticvoidmain(String[]args){//分別創(chuàng)建兩個(gè)線程對(duì)象,用于循環(huán)輸出語句Threadthread1=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":abcd"+i);if(i==1){try{//線程在執(zhí)行過程睡眠500毫秒,則線程進(jìn)入限時(shí)等待狀態(tài)Thread.sleep(500);}catch(InterruptedExceptione){e.printStackTrace();}}}}},"線程1");Threadthread2=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":1234");}}},"線程2");//分別開啟兩個(gè)線程thread1.start();thread2.start();}}例8-5Example8_5.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.1線程的休眠注意:
當(dāng)某線程調(diào)用sleep()方法后,該線程放棄CPU使用權(quán),在指定的時(shí)間段內(nèi),該線程不會(huì)獲得執(zhí)行的機(jī)會(huì)。只有當(dāng)休眠時(shí)間結(jié)束后,線程才會(huì)轉(zhuǎn)換到就緒狀態(tài),等待再次獲得CPU使用權(quán)執(zhí)行。休眠狀態(tài)下的線程不會(huì)釋放同步鎖/同步監(jiān)聽器。任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.3線程的讓步
線程讓步是通過調(diào)用方法yield()來實(shí)現(xiàn),它能讓當(dāng)前線程由“運(yùn)行狀態(tài)”進(jìn)入到“就緒狀態(tài)”,從而讓其它具有相同優(yōu)先級(jí)的等待線程獲取執(zhí)行權(quán);但是,并不能保證在當(dāng)前線程調(diào)用yield()之后,其它具有相同優(yōu)先級(jí)的線程就一定能獲得執(zhí)行權(quán);也有可能是當(dāng)前線程又進(jìn)入到“運(yùn)行狀態(tài)”繼續(xù)運(yùn)行。
下面我們通過一個(gè)案例來查看yield()方法的使用情況,如例:8-6所示。例8-6Example8_6.java任務(wù)8.2優(yōu)化多窗口售票程序//自定義一個(gè)線程類,繼承自Thread類classMyYieldThreadextendsThread{//構(gòu)造方法,初始化線程名publicMyYieldThread(Stringname){super(name);}//重寫run()方法,方法內(nèi)是線程要執(zhí)行的語句publicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":aaaa-"+i);if(i==1){System.out.println("線程讓步:");Thread.yield();//線程暫行執(zhí)行,做出讓步}}}}publicclassExample8_6{publicstaticvoidmain(String[]args){//分別創(chuàng)建兩個(gè)線程對(duì)象,用于循環(huán)輸出語句MyYieldThreadmyYieldThread1=newMyYieldThread("myYieldThread1");MyYieldThreadmyYieldThread2=newMyYieldThread("myYieldThread2");//分別開啟兩個(gè)線程myYieldThread1.start();myYieldThread2.start();}}例8-6Example8_6.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序8.2.2.4線程的插隊(duì)
線程的插隊(duì)是通過調(diào)用join()方法來實(shí)現(xiàn)。當(dāng)在某個(gè)線程中調(diào)用其他線程的join()方法,則調(diào)用的線程被阻塞,直到被join方法加入的線程執(zhí)行完成后它才會(huì)繼續(xù)執(zhí)行。
下面我們通過一個(gè)案例來查看join()方法的使用情況,如例:8-7所示。例8-7Example8_7.java任務(wù)8.2優(yōu)化多窗口售票程序//自定義一個(gè)線程類,繼承自Thread類classMyJoinThreadextendsThread{//構(gòu)造方法,初始化線程名publicMyJoinThread(Stringname){super(name);}//重寫run()方法,方法內(nèi)是線程要執(zhí)行的語句publicvoidrun(){for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":aaaa-"+i);}}}publicclassExample8_7{publicstaticvoidmain(String[]args)throwsInterruptedException{//分別創(chuàng)建兩個(gè)線程對(duì)象,用于循環(huán)輸出語句MyJoinThreadmyJoinThread1=newMyJoinThread("myJoinThread1");myJoinThread1.start();for(inti=0;i<5;i++){System.out.println(Thread.currentThread().getName()+":bbbb-"+i);if(i==1){myJoinThread1.join();//線程插隊(duì)}}}}例8-7Example8_7.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序8.2.3多線程同步
多線程并發(fā)執(zhí)行可以提高程序的效率,但是當(dāng)多個(gè)線程訪問同一共享資源時(shí),也會(huì)引發(fā)安全問題。8.2.3.1線程安全
例如:前面的售票程序,在實(shí)際售票的時(shí)候,每個(gè)售票窗口在出售票的時(shí)候都是有一定耗時(shí)的,則在耗時(shí)過程中可能會(huì)導(dǎo)致同一張票被多個(gè)窗口銷售,也可能會(huì)出現(xiàn)被銷售多出20張的票。
下面我們通過修改前面的任務(wù)1的程序Auto_Ticketing2查看線程安全問題,修改后的程序?yàn)椋耗M售票有耗時(shí)的情況Auto_Ticketing3.java任務(wù)8.2優(yōu)化多窗口售票程序classTicketWindow3_RunableimplementsRunnable{privateinttickets=20;//實(shí)現(xiàn)接口方法run()publicvoidrun(){while(tickets>0){try{Thread.sleep(1000);//模擬售票等待過程}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}}}
publicclassAuto_Ticketing3{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow_Runable的實(shí)例對(duì)象TicketWindow3_RunableticketWindow=newTicketWindow3_Runable();//創(chuàng)建三個(gè)子線程用于售票newThread(ticketWindow,"售票窗口1").start();newThread(ticketWindow,"售票窗口2").start();newThread(ticketWindow,"售票窗口3").start();}}模擬售票有耗時(shí)的情況:Auto_Ticketing3.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.1線程安全說明:
從上面運(yùn)行結(jié)果可以看到在多線程售票中出現(xiàn)了安全問題:由于線程有延遲,售票窗口3正在銷售第6張票,此時(shí)共享資源的剩余票是6張,如果在售票窗口1此時(shí)獲得了CPU使用權(quán),發(fā)現(xiàn)票數(shù)是6張,也會(huì)銷售第6張票。同理,當(dāng)售票窗口正在銷售最后1張票時(shí),在線程延遲中,其他售票窗口線程也可能會(huì)獲得CPU使用權(quán),則判斷時(shí)發(fā)現(xiàn)剩余票數(shù)是1張,大于0,則仍然可以繼續(xù)售票,就可能會(huì)出現(xiàn)銷售第0,第-1張票的情況。
從上述程序運(yùn)行結(jié)果可以看到,線程安全問題主要是由多個(gè)線程同時(shí)處理共享資源造成的。如果要解決這個(gè)安全問題,須要保證在同一時(shí)刻只能有一個(gè)線程訪問共享資源。Java中提供了線程同步機(jī)制來解決線程安全問題。任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.2線程同步
Java中提供了三種線程同步方式來解決線程安全問題:一種是同步代碼塊,一種是同步方法,還有一種是同步鎖。(1)同步代碼塊
同步代碼塊,是當(dāng)多個(gè)線程使用同一個(gè)共享資源時(shí),將處理共享資源的代碼放置在關(guān)鍵字synchronized修飾的代碼塊中,這段代碼被稱為同步代碼塊。其語法格式如下:synchronized(lock){ //需要同步操作的代碼}任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.2線程同步(1)同步代碼塊
說明:
同步代碼塊格式中,synchronized代表同步,lock是一個(gè)同步鎖,可以是任意類型的對(duì)象,但是多個(gè)線程共享的同步鎖對(duì)象必須是相同的,且在任何時(shí)候,最多允許使用共享資源的多個(gè)線程中的一個(gè)線程擁有同步鎖,誰獲得同步鎖就可以進(jìn)入執(zhí)行代碼塊。
當(dāng)線程A執(zhí)行同步代碼塊時(shí),首先檢查同步鎖對(duì)象的標(biāo)志位,默認(rèn)情況下標(biāo)志位為1,此時(shí)線程A可以執(zhí)行同步代碼塊,同時(shí)將所對(duì)象的標(biāo)志位置為0。當(dāng)另外一個(gè)線程B執(zhí)行到同步代碼快時(shí),由于鎖對(duì)象標(biāo)志位為0,線程B阻塞,等待當(dāng)前線程A執(zhí)行完同步代碼塊后,把鎖對(duì)象標(biāo)志位置為1,線程B才能執(zhí)行同步代碼塊。任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.2線程同步(2)同步方法
在一個(gè)方法前面加上關(guān)鍵字synchronized進(jìn)行修飾,被修飾的方法稱為同步方法。語法格式如下:
說明:
同步方法在同一時(shí)刻只允許一個(gè)線程訪問,訪問該方法的其他線程都會(huì)被阻塞,直到當(dāng)前線程訪問執(zhí)行完畢后,其他線程才有機(jī)會(huì)訪問執(zhí)行。[修飾符]synchronized返回值類型方法名([參數(shù)1,…]){ //需要同步操作的代碼}任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.2線程同步(3)同步鎖
同步代碼塊和同步方法使用的是封閉式鎖機(jī)制,它無法中斷一個(gè)正在等候獲得鎖的線程,也無法通過輪詢得到鎖。每個(gè)線程在執(zhí)行同步代買的時(shí)候,每次都需要判斷鎖的狀態(tài),資源消耗比較大,性能比不用要低一些。故在編程時(shí)建議盡量減少synchronized的作用域。
從JDK1.5開始,Java提供了比同步代碼塊和同步方法更廣泛的鎖操作,是一個(gè)功能更強(qiáng)大的Lock鎖,既具有同步代碼塊和同步方法的功能,同時(shí)在使用時(shí)也更靈活,且可以讓線程釋放鎖。
任務(wù)8.2優(yōu)化多窗口售票程序8.2.3.2線程同步(3)同步鎖
同步鎖Lock是一個(gè)接口,它的實(shí)現(xiàn)類是ReentrantLock。在編程中最常用的是創(chuàng)建一個(gè)同步鎖對(duì)象,對(duì)代碼塊進(jìn)行上鎖和解鎖操作。如下所示:說明:上鎖使用的是方法lock(),解鎖使用的是方法unlock(),可以在需要的位置靈活的上鎖解鎖。除此之外,ReentrantLock類還提供的有其他對(duì)鎖的操作方法。privatefinalLocklock=newReentrantLock();//創(chuàng)建同步鎖對(duì)象……lock.lock();//上鎖 //需要同步操作的代碼lock.unlock();//解鎖……任務(wù)實(shí)施根據(jù)任務(wù)分析可知:(1)模擬售票窗口耗時(shí)可以使用前面學(xué)習(xí)的線程休眠方法sleep()來實(shí)現(xiàn);(2)為確保線程安全,既可以使用前面學(xué)習(xí)的同步代碼塊來實(shí)現(xiàn),也可以使用同步方法來實(shí)現(xiàn)多窗口售票程序,當(dāng)然也可以使用同步鎖來實(shí)現(xiàn)線程安全的售票。任務(wù)8.2優(yōu)化多窗口售票程序任務(wù)8.2優(yōu)化多窗口售票程序//創(chuàng)建類實(shí)現(xiàn)接口RunnableclassTicketWindow4_RunableimplementsRunnable{privateinttickets=20;Objectlock=newObject();//實(shí)現(xiàn)接口方法run()publicvoidrun(){while(true){synchronized(lock){if(tickets>0){try{Thread.sleep(1000);//模擬售票等待過程}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}}}}}
publicclassAuto_Ticketing4{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow_Runable的實(shí)例對(duì)象TicketWindow4_RunableticketWindow=newTicketWindow4_Runable();//創(chuàng)建三個(gè)子線程用于售票newThread(ticketWindow,"售票窗口1").start();newThread(ticketWindow,"售票窗口2").start();newThread(ticketWindow,"售票窗口3").start();}}任務(wù)實(shí)現(xiàn)方法1:同步代碼塊Auto_Ticketing4.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序//創(chuàng)建類實(shí)現(xiàn)接口RunnableclassTicketWindow5_RunableimplementsRunnable{privateinttickets=20;//實(shí)現(xiàn)接口方法run()publicvoidrun(){while(true){saleTicket();}}privatesynchronizedvoidsaleTicket(){if(tickets>0){try{Thread.sleep(1000);//模擬售票等待過程}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}}}
publicclassAuto_Ticketing5{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow_Runable的實(shí)例對(duì)象TicketWindow5_RunableticketWindow=newTicketWindow5_Runable();//創(chuàng)建三個(gè)子線程用于售票newThread(ticketWindow,"售票窗口1").start();newThread(ticketWindow,"售票窗口2").start();newThread(ticketWindow,"售票窗口3").start();}}任務(wù)實(shí)現(xiàn)方法2:同步方法之Auto_Ticketing5.java運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;//創(chuàng)建類實(shí)現(xiàn)接口RunnableclassTicketWindow6_RunableimplementsRunnable{privateinttickets=20;privatefinalLocklock=newReentrantLock();//實(shí)現(xiàn)接口方法run()publicvoidrun(){while(true){lock.lock();if(tickets>0){try{Thread.sleep(1000);//模擬售票等待過程System.out.println(Thread.currentThread().getName()+"正在銷售第"+tickets--+"張票");}catch(InterruptedExceptione){e.printStackTrace();}finally{lock.unlock();}}}}}
publicclassAuto_Ticketing6{publicstaticvoidmain(String[]args){//創(chuàng)建子線程類TicketWindow_Runable的實(shí)例對(duì)象TicketWindow6_RunableticketWindow=newTicketWindow6_Runable();//創(chuàng)建三個(gè)子線程用于售票newThread(ticketWindow,"售票窗口1").start();newThread(ticketWindow,"售票窗口2").start();newThread(ticketWindow,"售票窗口3").start();}}任務(wù)實(shí)現(xiàn)方法3:同步鎖之Auto_Ticketing6.java
運(yùn)行結(jié)果如下圖所示:任務(wù)8.2優(yōu)化多窗口售票程序三種任務(wù)實(shí)現(xiàn)方式對(duì)比:
從上述運(yùn)三種實(shí)現(xiàn)方法可以看到任何一種方法都可以實(shí)現(xiàn)任務(wù)需求。編程過程中可以根據(jù)需要確定使用同步代碼塊/同步方法/同步鎖的任意一種來實(shí)現(xiàn)線程安全需要。同步鎖會(huì)更靈活,功能更強(qiáng)大。實(shí)踐訓(xùn)練按要求完成如下程序。(1)實(shí)現(xiàn)三個(gè)同學(xué)分吃10塊蛋糕的應(yīng)用;
(2)需要模擬分吃的過程的延遲,且確保線程安全;(3)顯示分吃過程,例如:“同學(xué)A分吃了蛋糕10”“同學(xué)B分吃了蛋糕9”等。任務(wù)8.2優(yōu)化多窗口售票程序任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)任務(wù)3目標(biāo)掌握線程的等待和喚醒了解多線程通信任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)任務(wù)3描述在現(xiàn)實(shí)生活中,生產(chǎn)者負(fù)責(zé)生產(chǎn)商品,消費(fèi)者負(fù)責(zé)消費(fèi)商品,當(dāng)生產(chǎn)者生產(chǎn)商品后,消費(fèi)者才能消費(fèi)商品。運(yùn)行效果如圖8-15所示。圖8-15任務(wù)3運(yùn)行效果圖任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)知識(shí)準(zhǔn)備多線程通信 Java中不同的線程執(zhí)行不同的任務(wù),如果任務(wù)之間有某種關(guān)系,線程間必須能夠通信,協(xié)調(diào)完成工作。為了讓線程間能進(jìn)行協(xié)調(diào)工作,就需要線程間能進(jìn)行通信。Java提供了線程間通信常用的三個(gè)方法是:wait(),nofity(),notifyAll(),用于線程的等待與喚醒。voidwait(),讓當(dāng)前線程放棄同步鎖并進(jìn)入等待狀態(tài),直到其他線程進(jìn)入此同步鎖,并調(diào)用nofity()方法或notifyAll()方法喚醒該線程為止。wait(longtimeout),讓當(dāng)前線程放棄同步鎖并進(jìn)入等待狀態(tài),直到其他線程調(diào)進(jìn)入此同步鎖,并調(diào)用notify()方法或notifyAll()方法,或者超過指定的時(shí)間量,當(dāng)前線程被喚醒進(jìn)入就緒狀態(tài)。notify(),喚醒在此同步鎖上等待的單個(gè)線程。notifyAll(),喚醒在此同步鎖上等待的所有線程。任務(wù)實(shí)施根據(jù)任務(wù)分析可知:(1)生產(chǎn)者和消費(fèi)者操作的是共享資源,需要定義共享資源類,并在共享資源類中定義生產(chǎn)和取出數(shù)據(jù)的方法;(2)生產(chǎn)者需要定義一個(gè)線程類;(3)消費(fèi)者需要定義一個(gè)線程類;(4)編寫測(cè)試類分別調(diào)用生產(chǎn)者和消費(fèi)者實(shí)現(xiàn)生產(chǎn)消費(fèi)交替完成。任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)//共享資源類classShareResource{privateStringgoods_No;privateStringgoods_Name;//生產(chǎn)存入數(shù)據(jù)publicvoidpush(Stringgoods_No,Stringgoods_Name){this.goods_No=goods_No;try{Thread.sleep(20);//模擬網(wǎng)絡(luò)延遲}catch(InterruptedExceptione){e.printStackTrace();}this.goods_Name=goods_Name;System.out.println("生產(chǎn)者生產(chǎn):"+this.goods_No+"-"+this.goods_Name);}//消費(fèi)取出數(shù)據(jù)publicvoidpopup(){try{Thread.sleep(20);//模擬網(wǎng)絡(luò)延遲}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("消費(fèi)者消費(fèi):"+this.goods_No+"-"+this.goods_Name);}}任務(wù)實(shí)現(xiàn)方法1:
Productive_Consumption.java任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)//生產(chǎn)者線程類classProducerimplementsRunnable{privateShareResourceshareResource;publicProducer(ShareResourceshareResource){this.shareResource=shareResource;}@Overridepublicvoidrun(){for(inti=1;i<=10;i++){if(i%2==0)//當(dāng)循環(huán)變量奇偶數(shù)不同生產(chǎn)的數(shù)據(jù)也不同shareResource.push("貨物編號(hào)"+i,"貨物名稱"+i);elseshareResource.push("商品編號(hào)"+i,"商品名稱"+i);}}}任務(wù)實(shí)現(xiàn)方法1:
Productive_Consumption.java任務(wù)8.3模擬“生產(chǎn)-消費(fèi)”程序設(shè)計(jì)//消費(fèi)者線程類classConsumerimplementsRunnable{privateShareResourceshareResource;publicConsumer(ShareResourceshareResource){this.shareResource=shareResource;}@Overridepublicvoidrun(){for(inti=1;i<=10;i++){shareResource.popup();//消費(fèi)取出數(shù)據(jù)}}}任務(wù)實(shí)現(xiàn)方法1:
Productive_Consump
溫馨提示
- 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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024奶牛養(yǎng)殖基地施工承包協(xié)議
- 2024暑期工勤工儉學(xué)勞動(dòng)協(xié)議示例
- 2024年借款居間協(xié)議格式樣本
- 2024年度采石場(chǎng)租賃運(yùn)營權(quán)轉(zhuǎn)移協(xié)議
- 2024陶瓷燒制加工承攬協(xié)議
- 2024專業(yè)居間服務(wù)借款協(xié)議范本
- 2024年適用珠寶銷售協(xié)議模板
- 2024年度活動(dòng)策劃服務(wù)協(xié)議
- 2024股權(quán)抵押個(gè)人貸款協(xié)議范本
- 2024年運(yùn)營流程再造與維護(hù)服務(wù)協(xié)議
- 2024年國家公務(wù)員考試行測(cè)真題卷行政執(zhí)法答案和解析
- 2023-2024學(xué)年北京市清華附中朝陽學(xué)校七年級(jí)(上)期中數(shù)學(xué)試卷【含解析】
- 北京三甲中醫(yī)疼痛科合作方案
- 《夏天里的成長(zhǎng)》語文教學(xué)PPT課件(6篇)
- 《駝鹿消防員的一天》課件
- 小學(xué)思政課《愛國主義教育》
- 農(nóng)機(jī)修理工培訓(xùn)大綱
- 新視野第三冊(cè)網(wǎng)測(cè)習(xí)題和答案資料
- 新時(shí)代企業(yè)戰(zhàn)略管理制度轉(zhuǎn)變與創(chuàng)新
- 火鍋連鎖餐飲連鎖餐廳運(yùn)營資料 海底撈 杯具清洗消毒流程P1
- 現(xiàn)代農(nóng)業(yè)產(chǎn)業(yè)創(chuàng)投基金組建方案
評(píng)論
0/150
提交評(píng)論