Java開發(fā)綜合實戰(zhàn) 教案 項目九 多線程技術_第1頁
Java開發(fā)綜合實戰(zhàn) 教案 項目九 多線程技術_第2頁
Java開發(fā)綜合實戰(zhàn) 教案 項目九 多線程技術_第3頁
Java開發(fā)綜合實戰(zhàn) 教案 項目九 多線程技術_第4頁
Java開發(fā)綜合實戰(zhàn) 教案 項目九 多線程技術_第5頁
已閱讀5頁,還剩16頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

Java開發(fā)綜合實戰(zhàn)STYLEREFbt1a項目九STYLEREFbt1b多線程技術項目九多線程技術思政目標尊重事物的發(fā)展規(guī)律,學會科學地分配作息時間。厚植報國情懷,使課程知識與思政同向同行、齊頭并進技能目標能夠通過繼承Thread類和實現(xiàn)Runnable接口兩種方法創(chuàng)建線程能夠根據(jù)實際需求轉換線程的狀態(tài)能夠實現(xiàn)線程同步并協(xié)調同步的線程項目導讀在現(xiàn)實生活中,很多事情都是同時進行的。Java引入了線程機制,可以同時運行不同的程序塊,不僅程序運行更為順暢,同時也可達到多任務處理的目的。掌握多線程技術,可以充分利用CPU的資源,提升單位時間內的程序處理性能,是現(xiàn)代程序開發(fā)中高并發(fā)的主要設計形式。本項目將詳細介紹多線程的相關知識,主要內容包括線程的創(chuàng)建、線程的狀態(tài)、線程的操作方法,以及線程的同步與交互等。任務1實現(xiàn)Java多線程任務引入既然開發(fā)了網絡版的應用程序,就有可能涉及多人同時操作管理系統(tǒng)的問題,這就需要用到多線程技術。小白知道多線程是Java的重要特性之一,什么是線程呢?在Java中如何實現(xiàn)多線程呢?知識準備在Java中,線程是一種繼承了Thread類或者實現(xiàn)了Runnable接口的對象。對應地,創(chuàng)建線程有兩種方式:一種是繼承Thread類,另一種是實現(xiàn)Runnable接口。一、進程與線程在Windows中,每一個在操作系統(tǒng)中運行的應用程序都是一個進程(process)。進程是系統(tǒng)進行資源分配和調度的基本單位。每一個進程都有獨立的內存空間和系統(tǒng)資源,其內部數(shù)據(jù)和狀態(tài)也是完全獨立的。程序從加載、執(zhí)行到執(zhí)行完畢的過程,就是進程從產生、發(fā)展到消亡的過程。CPU同一時刻只能運行一個進程,利用CPU的分時機制,每個進程都能循環(huán)獲得自己的CPU時間片。由于CPU的輪換速度非??欤灾劣谟脩舾杏X不到進程的中斷,產生所有程序“同時”運行的錯覺。線程是進程的組成部分,也稱為輕量級進程(LWP),一個線程是進程中的一個執(zhí)行流程。一個進程中可以同時包括多個線程,每個線程本身不擁有系統(tǒng)資源,只擁有少量在運行中必不可少的資源,但它可與同屬一個進程的其他線程共享進程所擁有的全部資源。每個線程都可以得到一小段程序的執(zhí)行時間,這樣一個進程就可以具有多個并發(fā)執(zhí)行的線程。每個Java應用程序都有一個缺省的主線程(main線程)。這個主線程就是JVM加載代碼發(fā)現(xiàn)main()方法后啟動的線程,負責執(zhí)行main()方法。在單線程中,程序代碼按調用順序依次往下執(zhí)行,執(zhí)行完最后一個語句,JVM就會結束Java應用程序。如果需要一個進程同時完成多段代碼的操作,就需要在main()方法的執(zhí)行中再創(chuàng)建其他線程,也就是產生多線程。JVM在主線程和其他線程之間輪流切換,每個線程都有機會獲得CPU資源。這種情況下,如果main線程結束,JVM也不會結束應用程序,而是等到應用程序中的所有線程都結束之后,才結束應用程序。二、線程的狀態(tài)為方便操作系統(tǒng)管理線程,線程要經歷不同的生命階段。線程被創(chuàng)建以后,CPU需要在多條線程之間切換,因此線程既不是一啟動就進入執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和消亡(Dead)五種狀態(tài)。1.新建狀態(tài)一個線程對象被聲明并創(chuàng)建時,該線程就處于新建狀態(tài),此時僅由JVM為其分配內存空間,并初始化其成員變量的值。2.就緒狀態(tài)線程對象調用start()方法之后,該線程就處于就緒狀態(tài),進入線程隊列排列,等待調度運行。3.運行狀態(tài)如果處于就緒狀態(tài)的線程獲得了CPU資源,就開始執(zhí)行run()方法中的線程執(zhí)行體,此時該線程處于運行狀態(tài)。run()方法規(guī)定了線程的具體使命。在線程的run()方法結束之前,不能再調用該線程的start()方法,否則會發(fā)生IllegalThreadStateException異常。4.阻塞狀態(tài)當處于運行狀態(tài)的線程失去所占用資源之后,便進入阻塞狀態(tài)。在Java中,線程進入阻塞狀態(tài)可能有以下4種原因。(1)JVM將CPU資源切換給其他線程。(2)線程執(zhí)行了sleep(intmillsecond)方法進入休眠狀態(tài)。經過指定的時間millsecond之后,該線程將重新進入線程隊列等候CPU資源,以便從中斷處繼續(xù)運行。(3)線程執(zhí)行了wait()方法進入等待狀態(tài)。這種情況下,必須由其他線程調用notify()方法通知它重新進入線程隊列等候CPU資源,以便從中斷處繼續(xù)運行。(4)線程執(zhí)行某個操作(例如運行耗時的I/O操作)進入阻塞狀態(tài)。這種情況下,只有當引起阻塞的原因消除時,該線程才會重新進入線程隊列等候CPU資源,以便從中斷處繼續(xù)運行。5.消亡狀態(tài)線程因異常被強制結束或執(zhí)行完run()方法,線程結束生命周期就會處于消亡狀態(tài)。此時的線程已釋放分配給它的內存,不再具有繼續(xù)運行的能力。三、繼承Thread類實現(xiàn)多線程Thread類是java.lang包中的一個類,在使用時不需要引入java.lang包,系統(tǒng)會自動加載。使用Thread類實現(xiàn)線程的步驟如下:(1)繼承Thread類,并重寫run()方法。Thread類中的run()方法沒有具體內容,需要在子類中重寫run()方法規(guī)定線程要完成的具體任務。run()方法通常也稱為線程執(zhí)行體。(2)創(chuàng)建Thread子類的實例,即創(chuàng)建線程對象。Thread類創(chuàng)建線程有以下兩個常用的構造方法:publicThread():創(chuàng)建一個線程對象。publicThread(StringthreadName):創(chuàng)建一個有指定名稱的線程對象。(3)調用線程對象的start()方法啟動線程。線程對象創(chuàng)建之后,不會自動進入線程隊列,JVM也不知道它的存在。此時需要調用start()方法啟動線程,進入線程隊列等候執(zhí)行。當線程獲得CPU資源時,run()方法就會即刻執(zhí)行,進入運行狀態(tài)。案例——模擬喂養(yǎng)寵物本案例通過繼承Thread類創(chuàng)建兩個線程,輸出寵物吃東西、喝水,使用主線程輸出寵物玩耍,演示多線程的運行效果。(1)在Eclipse中新建一個名為ThreadDemo的Java項目。然后在項目中添加一個名為EatFood的類,該類繼承Thread類,并重寫run()方法。具體代碼如下:publicclassEatFoodextendsThread{ privateintfoodamount=5; //食物總量 publicvoidrun(){ //重寫run() while(foodamount>0){ for(inti=1;i<=5;i++){ System.out.print("第"+i+"次吃東西\t"); foodamount-=1; } System.out.println("食物吃完啦!"); } }}(2)在項目中添加一個名為Drink的類,繼承Thread類,并重寫run()方法。具體代碼如下:publicclassDrinkextendsThread{ privateintwateramount=3; //水的總量 publicvoidrun(){ //重寫run()方法 while(wateramount>0){ for(inti=1;i<=3;i++){ System.out.print("第"+i+"次喝水\t"); wateramount-=1; } System.out.println("水喝完啦!"); } }}(3)在項目中添加一個名為TestThread的類,編寫主方法實例化線程對象,然后啟動線程。具體代碼如下:publicclassTestThread{ publicstaticvoidmain(String[]args){ //調用無參構造方法創(chuàng)建兩個線程 EatFoodeat=newEatFood(); Drinkdrink=newDrink(); //啟動線程 eat.start(); drink.start(); //主線程的循環(huán)語句 for(inti=1;i<=4;i++) System.out.print("第"+i+"次玩耍\t"); }}(4)運行程序TestThread.java,在控制臺窗格中可以看到多線程的運行結果,如圖9-1所示。圖9-1運行結果提示:上述程序的運行結果取決于當前CPU資源的使用情況,因此每次運行的結果都可能不同。在運行程序TestThread.java時,JVM首先進入main()方法啟動主線程,主線程在使用CPU資源時實例化并啟動兩個線程,然后執(zhí)行for循環(huán)。在本例的這次運行中,主線程執(zhí)行1次for循環(huán)語句后,CPU資源切換給線程drink。線程drink運行run()方法,執(zhí)行for循環(huán)3次后,CPU資源切換給線程eat,該線程運行run()方法,執(zhí)行1次for循環(huán)后,CPU資源又切換給線程drink,輸出“水喝完啦!”,至此,線程drink的run()方法執(zhí)行完成,進入消亡狀態(tài)。CPU資源切換給主線程執(zhí)行一次for循環(huán),然后切換給線程eat執(zhí)行3次for循環(huán)。接下來又切換給主線程執(zhí)行兩次for循環(huán),至此,主線程的執(zhí)行體運行完成進入消亡狀態(tài)。但JVM并沒有結束應用程序,而是將CPU資源切換給線程eat從中斷處繼續(xù)執(zhí)行,直到該線程的run()方法執(zhí)行完成,所有線程都進入消亡狀態(tài),應用程序才結束運行。四、使用Runnable接口實現(xiàn)多線程線程對象除了可以繼承Thread類創(chuàng)建,還可以通過實現(xiàn)Runnable接口創(chuàng)建。與前一種方式相比,使用Runnable接口方式可以避免單繼承的局限,在繼承非Thread類時實現(xiàn)多線程。使用Runnable接口實現(xiàn)線程的步驟如下:(1)定義Runnable接口的實現(xiàn)類,并重寫該接口的run()方法。Runnable接口中只定義了一個抽象方法run()方法,事實上Thread類也是Runnable接口的一個實現(xiàn)類,Thread類的run()方法是Runnable接口的run()方法的重寫。也就是說,上一節(jié)中繼承Thread類重寫的run()方法實際上重寫的是Runnable接口的run()方法。(2)創(chuàng)建Runnable接口實現(xiàn)類的實例作為線程對象的運行對象,傳遞給構造方法創(chuàng)建線程對象。Runnable接口的實現(xiàn)類采用以下兩種構造方法創(chuàng)建Thread對象。publicThread(Runnabletarget):使用實現(xiàn)了Runnable接口的類對象target作為運行對象,創(chuàng)建一個線程對象。publicThread(Runnabletarget,Stringname):使用一個有指定名稱的對象target作為運行對象,創(chuàng)建線程對象。(3)調用線程對象的start()方法啟動線程。案例——模擬外賣訂單本案例通過實現(xiàn)Runnable接口創(chuàng)建一個線程,輸出一個外賣訂單,使用主線程輸出一個訂單,演示多線程的運行效果。(1)在Java項目ThreadDemo中添加一個名為OrderThread的類,該類實現(xiàn)Runnable接口,并重寫run()方法。具體代碼如下://定義接口Runnable的實現(xiàn)類publicclassOrderThreadimplementsRunnable{ publicvoidrun(){ //重寫run()方法 for(inti=1;i<=3;i++) System.out.println("套餐B"+i+"一份"); }}(2)在項目中添加一個名為TestRunnable的類,編寫主方法實例化線程對象,然后啟動線程。具體代碼如下:publicclassTestRunnable{ publicstaticvoidmain(String[]args){ OrderThreadtarget=newOrderThread(); //創(chuàng)建target Threadtread=newThread(target);//創(chuàng)建Thread對象 tread.start(); //啟動線程 for(inti=1;i<=3;i++) System.out.println("套餐A"+i+"一份"); }}(3)運行程序TestRunnable.java,在控制臺窗格中可以看到多線程的運行結果,如圖9-2所示。圖9-2運行結果任務2應用多線程任務引入通過上一個任務的學習,小白掌握了創(chuàng)建線程的方法,他明白,要想掌握多線程編程,就必須了解線程的生命周期,以及線程各種狀態(tài)的切換。此外,他還想知道如何將線程的特性應用于解決各類實際問題。知識準備一、線程的常用方法前面提到過,線程創(chuàng)建后,調用start()方法進入就緒狀態(tài),在線程隊列中排隊等待執(zhí)行;當線程獲取CPU資源時就自動執(zhí)行run()方法,進入運行狀態(tài)。在實際應用中,經常需要根據(jù)程序流程操作線程,強制使線程從某一種狀態(tài)轉換到另一種狀態(tài)。下面簡要介紹操作線程常用的幾個方法。1.start()方法該方法用于啟動線程,使新建狀態(tài)的線程轉換為就緒狀態(tài),進入線程隊列。讀者需要注意的是,只有處于新建狀態(tài)的線程才可以調用start()方法,調用之后不能再次調用該方法,否則會引起IllegalThreadStateException異常。2.run()方法該方法用于指定線程要執(zhí)行的具體操作,當線程獲得CPU資源后由系統(tǒng)自動調用該方法,使就緒狀態(tài)的線程進入運行狀態(tài)。該方法執(zhí)行完成之后,線程進入消亡狀態(tài)。3.sleep()方法默認情況下,線程按照優(yōu)先級從高到低的順序調度執(zhí)行,如果優(yōu)先級高的線程未消亡,優(yōu)先級低的線程就沒有機會獲得CPU資源。通過在高級別線程的run()方法中調用sleep()方法,可以使高級別的線程暫時進入休眠,從而低級別的線程有機會執(zhí)行。線程休眠的時間由sleep()方法的參數(shù)指定,單位為毫秒。休眠結束后,線程進入就緒狀態(tài)。sleep()是靜態(tài)方法,只對當前對象有效,讓當前線程進入休眠。注意:線程在休眠時如果被中斷,JVM會拋出InterruptedException異常。因此,必須在try-catch語句塊中調用sleep()。4.isAlive()方法該方法用于判斷線程是否啟動。處于新建狀態(tài)和消亡狀態(tài)的線程可調用isAlive()方法,返回false。如果線程進入運行狀態(tài),且run()方法沒有執(zhí)行完成,此時調用isAlive()方法返回true。5.currentThread()方法該方法是Thread類的一個靜態(tài)方法,返回當前正在使用CPU資源的線程。此時,可以通過getName()方法和setName()方法分別獲取、設置線程的名稱。如果沒有在線程啟動前設置名稱,系統(tǒng)會在使用時自動為線程分配一個形如“Thread-N”的名稱。6.interrupt()方法一個占有CPU資源的線程可以使用該方法“吵醒”(中斷)正在休眠的線程。此時休眠的線程會拋出InterruptedException異常,結束休眠,重新進入就緒狀態(tài)等候CPU資源。7.join()方法該方法可以強制某個線程運行,在此期間,其他線程進入阻塞狀態(tài),必須等待此線程消亡之后才可以繼續(xù)執(zhí)行。8.setPriority()方法在多線程環(huán)境下,系統(tǒng)根據(jù)線程的優(yōu)先級決定就緒狀態(tài)下哪個線程首先進入運行狀態(tài)。使用setPriority()方法可以設置線程的優(yōu)先級(1~10的正整數(shù))。Thread類使用常量表示線程的優(yōu)先級,例如MIN_PRIORITY代表數(shù)字1;MAX_PRIORITY代表數(shù)字10;NORM_PRIORITY代表數(shù)字5;如果沒有設置優(yōu)先級,則為默認值5。注意:線程的優(yōu)先級大小并不能決定線程的執(zhí)行順序,具體執(zhí)行順序由CPU的調度決定。9.yield()方法該方法可以暫停當前正在執(zhí)行的線程,使線程進入阻塞狀態(tài),其他具有相同優(yōu)先級的線程有進入運行狀態(tài)的機會。案例——模擬VIP插隊排號本案例使用join()方法阻塞當前線程,模擬銀行柜臺VIP插隊排號。(1)在Java項目ThreadDemo中新建一個名為JoinDemo的類,該類實現(xiàn)Runnable接口,重寫run()方法。然后編寫主方法,創(chuàng)建線程對象,使用join()方法合并線程。具體代碼如下://定義Runnable接口實現(xiàn)類publicclassJoinDemoimplementsRunnable{ //重寫run()方法 publicvoidrun(){ for(inti=0;i<2;i++){ //獲取線程名稱 System.out.println(Thread.currentThread().getName()+(i+1)+"號請到窗口辦理"); try{ Thread.sleep(2000); //休眠2秒 }catch(InterruptedExceptione){ e.printStackTrace(); } } } publicstaticvoidmain(String[]args){ //使用Runnable接口實現(xiàn)類的實例創(chuàng)建線程對象vip JoinDemotarget=newJoinDemo(); Threadvip=newThread(target); vip.setName("VIP客戶"); //設置線程名稱 vip.start(); for(inti=15;i<20;i++){ System.out.println((i+1)+"號客戶請到窗口辦理"); try{ vip.join(); //合并線程,強制運行線程vip,其他線程進入阻塞狀態(tài) }catch(InterruptedExceptione){ e.printStackTrace(); } } }}上面的代碼在重寫run()方法時,在for循環(huán)中調用currentThread()方法和getName()方法獲取當前占用CPU資源的線程名稱;使用sleep()方法使線程休眠2秒。在main()方法中創(chuàng)建線程對象vip后,調用setName()方法設置線程的名稱。然后啟動線程vip,在for循環(huán)中合并vip線程強制運行。(2)運行程序,在控制臺窗格中可以看到運行結果,如圖9-3所示。圖9-3運行結果從運行結果可以看到,線程vip調用join()方法之后,主線程進入阻塞狀態(tài),vip線程被強制執(zhí)行直到結束,然后繼續(xù)執(zhí)行主線程。二、實現(xiàn)線程同步在多線程程序中,由于多個線程搶占資源,可能會發(fā)生資源訪問的沖突,例如同時訪問同一個變量,且某個線程需要修改這個變量的值。為了避免這種沖突造成的數(shù)據(jù)不安全,Java提供了線程同步機制。所謂線程同步,是指一個線程調用使用關鍵字synchronized修飾的方法或代碼塊時,其他線程要使用這個代碼塊或方法就必須等待,直到上一個線程使用完該代碼塊或方法。實現(xiàn)線程同步有兩種方式:同步塊和同步方法。1.同步塊使用關鍵字synchronized修飾的代碼塊稱為同步塊,也稱為臨界區(qū),語法格式如下:synchronized(同步對象){//需要同步的代碼}從上面的語法格式可以看到,使用同步塊時必須指定一個需要同步的對象,通常將當前對象this設置為同步對象。每個對象都存在一個標志位,具有0和1兩個值。一個線程運行到同步塊時首先檢查該對象的標志位,如果為0,表明有其他線程在運行同步塊,此時該線程處于就緒狀態(tài)。運行同步塊的線程結束,同步對象的標志位被置為1,就緒狀態(tài)的線程開始執(zhí)行同步塊,并將同步對象的標志位設置為0。簡單來說,同步就是指一個線程必須等待另一個線程操作完再繼續(xù)的情況。需要同步的代碼通常為對共享資源的操作。2.同步方法所謂同步方法,就是使用關鍵字synchronized修飾多個線程都要使用的方法,語法格式如下:Synchronized返回值類型方法名(參數(shù)列表){//需要同步的代碼}某個對象調用同步方法時,該對象上的其他同步方法必須等到該同步方法執(zhí)行完畢后才能被執(zhí)行。案例——模擬景區(qū)售票因疫情防控,某景區(qū)開放了3個售票窗口實行限流售票。本案例使用同步代碼塊模擬景區(qū)售票。(1)在Java項目ThreadDemo中新建一個名為Tickets的類,該類實現(xiàn)Runnable接口,在重寫run()方法時定義同步代碼塊。具體代碼如下:publicclassTicketsimplementsRunnable{ intnum=10; //初始票數(shù) publicvoidrun(){ //重寫run()方法 while(num>0){ //定義同步塊,修改余票數(shù)量 synchronized(this){ if(num>0){ try{ Thread.sleep(500); //休眠0.5秒 }catch(Exceptione){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "售出一張\t余票:"+(--num)); //輸出售票信息和余票數(shù) } } } }}上面的代碼中,訪問和修改余票是各個線程的共享資源操作,因此放置在同步塊中。一個線程在處理余票數(shù)據(jù)時,其他線程暫時處于就緒狀態(tài),等前一個線程處理完成,余票數(shù)據(jù)減1,其他線程才能進入同步塊并處理余票數(shù)據(jù)。(2)在項目ThreadDemo中添加一個名為SaleTickets的類。編寫main()方法,創(chuàng)建3個線程并啟動線程。具體代碼如下:publicclassSaleTickets{ publicstaticvoidmain(Stringargs[]){ Ticketstickets=newTickets(); //創(chuàng)建Runnable實例tickets Thread[]td=newThread[3]; //使用數(shù)組存放3個線程 //創(chuàng)建線程,指定線程名稱,并啟動線程 for(inti=0;i<3;i++){ td[i]=newThread(tickets); td[i].setName((i+1)+"號窗口"); td[i].start(); } }}(3)運行SaleTickets.java,在控制臺窗格中可以看到輸出的售票信息以及余票信息,如圖9-4所示。圖9-4運行結果三、協(xié)調同步的線程對于同步,有時會涉及一些特殊情況。比如旅客排隊進站時,要求出示身份證明相關證件。如果有人一時找不到身份證件,相關人員就會要求他先在一旁查找,并允許他后面的人先驗證通過。如果找到了,他就可以從等待的位置開始驗證通行。在程序中,如果一個線程使用的同步方法要用到某個變量,而該變量又需要其他線程修改后才能使用,此時可以在同步方法中使用wait()方法中斷線程,使其等待,并允許其他線程執(zhí)行這個同步方法。其他線程執(zhí)行完這個同步方法時,使用notify()方法或notifyAll()方法通知等待執(zhí)行這個同步方法的線程進入就緒隊列,等待分配系統(tǒng)資源,以從中斷處開始執(zhí)行。如果有多個處于等待的線程,則遵循“先中斷先繼續(xù)”原則進入運行狀態(tài)。注意:wait()、notify()和notifyAll()方法只能用在同步塊或同步方法中,且不允許重寫。1.wait()方法該方法與sleep()方法的功能類似,在非多線程運行條件下的情況都是當前線程讓出執(zhí)行機會,進入休眠/等待。不同的是,線程調用wait()方法會釋放其占有的資源,從運行狀態(tài)轉換為等待狀態(tài),使得其他線程可以進入synchronized代碼塊執(zhí)行,且不需要捕獲異常。而調用sleep()方法不會釋放資源,其他線程只能等待synchronized代碼塊中的線程結束休眠并執(zhí)行完畢才能競爭,且必須要捕獲異常。提示:在實際應用中,wait()方法通常放在一個while(等待條件){}循環(huán)結構中。2.notify()方法該方法用于通知等待隊列中的第一個線程從等待狀態(tài)進入就緒狀態(tài)。3.notifyAll()方法該方法用于通知等待隊列中的所有線程從等待狀態(tài)進入就緒狀態(tài)。案例——排隊購買蛋糕顧客在蛋糕店排隊購買蛋糕,但現(xiàn)在只剩芝士蛋糕了,如果需要其他蛋糕,則需要等待。本案例利用wait()方法和notifyAll()方法模擬排隊買蛋糕的場景。(1)在Java項目ThreadDemo中新建一個名為Customers的類,該類實現(xiàn)Runnable接口,并定義一個同步方法處理購買蛋糕的事務。具體代碼如下:publicclassCustomersimplementsRunnable{ publicvoidrun(){ //重寫run()方法 //不同顧客調用同步方法byCake(),參數(shù)為要購買的蛋糕種類 if(Thread.currentThread().getName().equals("Lily")){ byCake("Bestingcake"); } elseif(Thread.currentThread().getName().equals("Alex")){ byCake("cheesecake"); } } //定義同步方法 privatesynchronizedvoidbyCake(Stringcake){ //cheesecake可以直接購買 if(cake=="cheesecake"){ System.out.println(Thread.currentThread().getName()+"要買"+cake); System.out.println(Thread.currentThread().getName()+"買到了"+cake); }else{ //如果要購買其他種類的蛋糕,輸出提示信息 try{ System.out.println(Thread.currentThread().getName()+"要買"+cake); System.out.println(cake+"已售完"); System.out.println(Thread.currentThread().getName()+"等待糕點師制作蛋糕……"); System.out.println("下一位顧客購買蛋糕……"); wait();//中斷當前線程,進入等待狀態(tài) Thread.sleep(2000);//休眠2秒,輸出提示信息 System.out.println(cake+"制作完成"); System.out.println(Thread.currentThread().getName()+"買到了"+cake); }catch(InterruptedExceptione){} } notifyAll(); //喚醒等待中的線程,從中斷處繼續(xù)執(zhí)行 }}(2)在項目中添加一個名為Cakes的類,該類創(chuàng)建兩個線程并啟動線程。具體代碼如下:publicclassCakes{ publicstaticvoidmain(String[]args){ Customerstarget=newCustomers(); //實例化Runnable實現(xiàn)類對象 Thread[]customers=newThread[2]; //使用數(shù)組存儲2個線程 String[]name=newString[]{"Lily","Alex"}; //指定線程名稱 //創(chuàng)建2個線程對象,設置名稱并啟動 for(inti=0;i<2;i++){ customers[i]=newThread(target); customers[i].setName(name[i]); customers[i].start(); } }}(3)運行程序,在控制臺窗格中可以看到運行結果,如圖9-5所示。圖9-5運行結果四、GUI線程在運行包含圖形用戶界面的Java應用程序時,JVM會自動啟動一些專門用來監(jiān)聽和響應用戶在圖形用戶界面上的操作的GUI線程。GUI線程負責建造窗體以及處理GUI事件,任何一個特定窗體的消息總是被產生這一窗體的線程捕獲,然后派發(fā)給不同的窗體函數(shù)處理。GUI線程中有兩個重要的線程:AWT-EventQuecue和AWT-Windows。AWT-EventQuecue線程負責處理GUI事件,而AWT-Windows線程負責將窗體或組件繪制到桌面。在創(chuàng)建有GUI的Java多線程應用程序時,通常會繼承JFrame類,并實現(xiàn)Runnable接口和需要的事件監(jiān)聽接口。案例——字母游戲本案例利用Swing組件、GUI事件和多線程,制作一個字母游戲。(1)在Java項目ThreadDemo中新建一個名為RandomLetter的類,該類繼承JFrame類,并實現(xiàn)接口ActionListener和Runnable。具體代碼如下:importjava.awt.Color;importjava.awt.FlowLayout;importjava.awt.Font;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjavax.swing.JFrame;importjavax.swing.JLabel;importjavax.swing.JTextField;//繼承JFrame,并實現(xiàn)接口ActionListener和RunnablepublicclassRandomLetterextendsJFrameimplementsActionListener,Runnable{ JLabelinfo,letter,score,msg; //標簽組件 JTextFieldinput; //文本域 ThreadsetLetter; //用于產生字母 intsleepTime,scores; //休眠時間和得分 RandomLetter(){ setLayout(newFlowLayout()); //設置流式布局 setLetter=newThread(this); //創(chuàng)建線程 letter=newJLabel(""); //顯示生成的字母,初始為空 letter.setFont(newFont("Arial",Font.BOLD,26));//設置產生的字母的字體和顏色 letter.setForeground(Color.RED); add(letter); //添加到窗體 info=newJLabel("請輸入左側的字母(回車確認)"); add(info); input=newJTextField(6); //輸入的字母 add(input); score=newJLabel("得分:"); score.setForeground(Color.BLUE); add(score); msg=newJLabel(""); //錯誤提示信息 msg.setForeground(Color.RED); //默認字呈,顯示紅色 msg.setAlignmentY(CENTER_ALIGNMENT); //居中對齊 add(msg); input.addActionListener(this); //監(jiān)聽文本域的動作事件 setBounds(100,200,420,120); //窗體的位置和大小 setResizable(false); //窗體不可改變大小 setVisible(true); //窗體可見 setDefaultCloseOperation(EXIT_ON_CLOSE); //關閉窗體的方式 setLetter.start(); //在AWT-Windows線程中啟動產生字母的線程 } publicvoidsetSleepTime(intms){ //設置休眠時間 sleepTime=ms; } publicvoidrun(){ //重寫Runnable接口的run() while(true){ charc=(char)(int)(Math.random()*26+97);//生成隨機字母 letter.setText(""+c+""); //顯示字母 try{ Thread.sleep(sleepTime); //休眠 }catch(InterruptedExceptione){} } } //重寫ActionListener接口的actionPerformed() publicvoidactionPerformed(ActionEvente){ //獲取產生的字母和輸入的字母 Strings=letter.getText().trim(); Stringin=input.getText().trim(); if(s.equals(in)){ //如果輸入的字母正確 msg.setText(null); //清空錯誤提示 scores++; //計算得分 score.setText("得分:"+scores); //輸出得分 input.setText(null); //清空輸入文本框 setLerrupt(); //喚醒休眠的線程,產生新的字母 } else //如果輸入不正確,輸出錯誤提示 msg.setText("錯誤!請重新輸入!"); }}(2)在項目中添加一個名為LettersGame的類,編寫main()方法實例化類對象,具體代碼如下:publicclassLettersGame{ publicstaticvoidmain(String[]args){ RandomLetterwin=newRandomLetter(); win.setTitle("字母游戲"); //設置窗體標題 win.setSleepTime(5000); //設置休眠時間 }}(3)運行程序,打開如圖9-6所示的窗口。在文本框中輸入一個字母,如果與給定的字母相同(包括大小寫),則得分加1;如果輸入的字母與給定字母不同,則輸出一條錯誤提示信息,如圖9-7所示。圖9-6運行結果1圖9-7運行結果2本例中由于指定了產生字母的線程生成字母后休眠5秒,因此如果輸入錯誤或沒有輸入,則間隔5秒后顯示的字母會被新產生的字母替換。如果在5秒內輸入了正確的字母,則運行語句setLerrupt();,喚醒休眠的線程,立即產生新的字母。項目總結項目實戰(zhàn)本章將在上一章完成的項目基礎上進行修改,分別創(chuàng)建服務器端線程和客戶端線程。當一個客戶端訪問服務器時,就會新建一個線程處理這個客戶端的事務,從而可以處理多個用戶的請求。(1)復制并粘貼“進銷存管理系統(tǒng)V8.0”,在CopyProject對話框中修改項目名稱為“進銷存管理系統(tǒng)V9.0”,然后單擊Copy按鈕關閉對話框。(2)在net包中新建一個名為ServerThread.java的類,繼承Thread類,用于創(chuàng)建服務器端線程。具體代碼如下:packagenet;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;import.Socket;importcontroller.ServerControllers;importmodel.Goods;importmodel.Results;publicclassServerThreadextendsThread{ Socketconn; byte[]buf=newbyte[256]; InputStreamsin; OutputStreamsout; publicServerThread(Socketconn){ //構造方法 this.conn=conn; } publicvoidrun(){ //重寫run()方法 try{ System.out.println("已連接客戶端"+conn.getInetAddress() +",端口為"+conn.getPort()); sin=conn.getInputStream(); sout=conn.getOutputStream(); //處理請求 while(true){……}篇幅所限,省略了處理客戶端事務請求的代碼,具體代碼請參見上一章項目實戰(zhàn)中Server類的構造方法。(3)打開Server.java,修改Server類的構造方法如下: publicServer(){ ServerSocketss; Socketconn; //接收并處理客戶端請求 try{ ss=newServerSocket(2022); while(true){ System.out.println("服務器準備就緒,等待連接請求……"); conn=ss.accept(); //阻塞線程 ServerThreadst=newServerThread(conn); //創(chuàng)建線程 st.start(); //啟動線程 } }catch(IOExceptione){ e.printStackTrace(); } }(4)在net包中新建一個名為Client

溫馨提示

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

評論

0/150

提交評論