《Java程序設計項目化教程》課件-第11章_第1頁
《Java程序設計項目化教程》課件-第11章_第2頁
《Java程序設計項目化教程》課件-第11章_第3頁
《Java程序設計項目化教程》課件-第11章_第4頁
《Java程序設計項目化教程》課件-第11章_第5頁
已閱讀5頁,還剩66頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第11章任務11——設計考試系統(tǒng)中的倒計時11.1任務描述11.2技術概覽11.3任務實施 11.1任務描述

本章的任務是設計倒計時??荚囅到y(tǒng)中的倒計時功能是必不可少的功能之一,當考生成功登錄考試系統(tǒng)后,點擊【開始考試】按鈕,則計時系統(tǒng)開始倒計時。當考試時間結束時,系統(tǒng)將彈出相應的對話框提示并退出考試。如圖11-1所示,在我們所設計的考試系統(tǒng)中,時間的顯示在整個界面的上方,使得考生能清晰地看到時間的顯示,把握好考試時間。Java利用線程技術可以實現(xiàn)時間的動態(tài)刷新和顯示,從而可以實現(xiàn)時間的同步顯示。圖11-1倒計時運行效果 11.2技術要點

本章的技術要點是多線程技術。在傳統(tǒng)的程序設計中,程序運行的順序總是按照事先編制好的流程來執(zhí)行的,遇到if-else語句就加以判斷;遇到for、while語句若滿足循環(huán)條件就重復執(zhí)行相關語句。這種進程(程序)內(nèi)部的一個順序控制流稱為“線程”。到目前為止,我們所編寫的程序都是單線程運行的,也即在任意給定的時刻,只有一個單獨的語句在

執(zhí)行。多線程機制下則可以同時運行多個程序塊,相當于并行執(zhí)行程序代碼,使程序運行的效率變得更高。事實上,真正意義上的并行處理是在多處理器的前提下,同一時刻執(zhí)行多種任務。在單處理器的情況下,多線程通過CPU時間片輪轉(zhuǎn)來進行調(diào)度和資源分配,使得單個程序可以同時運行多個不同的線程,執(zhí)行不同的任務。由于CPU處理數(shù)據(jù)的速度極快,操作系統(tǒng)能夠在很短的時間內(nèi)迅速在各線程間切換執(zhí)行,因此看上去所有線程在同一時刻幾乎是同時運行的。多線程執(zhí)行的方式如圖11-2所示。圖11-2多線程執(zhí)行方式多線程是實現(xiàn)并發(fā)機制的一種有效手段。進程和線程一樣,都是實現(xiàn)并發(fā)性的一個基本單位。相對于線程,進程是程序的一次動態(tài)執(zhí)行過程,它對應了從代碼加載、執(zhí)行以及執(zhí)行完畢的一個完整過程,這個過程也是進程本身從產(chǎn)生、發(fā)展到消亡的過程。每一個進程的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨立的?;谶M程的多任務操作系統(tǒng)能同時運行多個進程(程序),例如在使用Word編輯文檔的同時可以播放音樂。

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

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

(2)每個進程都有一段專用的內(nèi)存區(qū)域。與此相反,線程卻共享內(nèi)存單元(包括代碼和數(shù)據(jù)),通過共享的內(nèi)存單元來實現(xiàn)數(shù)據(jù)交換、實時通信與必要的同步操作。

11.2.1線程的創(chuàng)建

在Java程序中,線程是以線程對象來表示的,也即在程序中,一個線程對象代表了一個可以執(zhí)行程序片段的線程。Java中提供了兩種創(chuàng)建線程的方法:擴展Thread類或?qū)崿F(xiàn)Runnable接口來創(chuàng)建線程。其中,Thread類和Runnable接口都定義在包java.lang中。

1.擴展Thread類以創(chuàng)建線程

直接定義Thread類的子類,重寫其中的run()方法,通過創(chuàng)建該子類的對象就可以創(chuàng)建線程。Thread類中包含了創(chuàng)建線程的構造函數(shù)以及控制線程的相關方法,如表11-1所示。表11-1Thread類的常用構造函數(shù)及方法創(chuàng)建和執(zhí)行線程的步驟如下:

(1)創(chuàng)建一個Thread類的子類,該類必須重寫Thread類的run()方法。

class類名稱extendsThread //從Thread類擴展出子類

{成員變量;

成員方法;

publicvoidrun() //重寫Thread類的run()方法

{線程處理的代碼

}

}

(2)創(chuàng)建該子類的對象,即創(chuàng)建一個新的線程。創(chuàng)建線程對象時會自動調(diào)用Thread類定義的相關構造函數(shù)。

(3)用構造函數(shù)創(chuàng)建新對象之后,這個對象中的有關數(shù)據(jù)被初始化,從而進入線程的新建狀態(tài),直到調(diào)用了該對象的start()方法。

(4)線程對象開始運行,并自動調(diào)用相應的run()方法。例11-1ThreadDemo1.java

1classMyThreadextendsThread{

2publicvoidrun(){

3for(inti=1;i<=10;i++)

4System.out.println(this.getName()+":"+i);

5}

6}

7publicclassThreadDemo1{

8publicstaticvoidmain(String[]args){

9MyThreadt=newMyThread();

10t.start(); 11}

12}

程序運行結果如圖11-3所示。圖11-3ThreadDemo1.java的運行結果從上例我們可以看到一個簡單的定義線程的過程,在此要注意run()方法是在線程啟動后自動被系統(tǒng)調(diào)用的,如果顯式地使用t.run()語句則方法調(diào)用將失去線程的功能。其中,Thread-0是默認的線程名,也可以通過setName()為其命名。

從程序及運行結果看,似乎僅存在一個線程。事實上,當Java程序啟動時,一個特殊的線程——主線程(mainthread)自動創(chuàng)建了,它的主要功能是產(chǎn)生其他新的線程,以及完成各種關閉操作。從例11-2中我們可以看到主線程和其他線程共同運行的情況。例11-2ThreadDemo2.java

1classMyThreadextendsThread{

2MyThread(Stringstr){

3 super(str);

4}

5publicvoidrun(){

6for(inti=1;i<=5;i++)

7System.out.println(this.getName()+":"+i);

8}

9}

10publicclassThreadDemo2{ 11publicstaticvoidmain(String[]args){

12MyThreadt1=newMyThread("線程1");

13MyThreadt2=newMyThread("線程2");

14t1.start();

15t2.start();

16for(inti=1;i<=5;i++)

17System.out.println(Thread.currentThread().getName()+":"+i);

18}

19}

程序運行結果如圖11-4所示。圖11-4ThreadDemo2.java多次運行產(chǎn)生的不同結果

2.實現(xiàn)Runnable接口以創(chuàng)建線程

上述通過擴展Thread類創(chuàng)建線程的方法雖然簡單,但是Java不支持多繼承,如果當前線程子類還需要繼承其他多個類,此時必須實現(xiàn)接口。Java提供了Runnable接口來完成創(chuàng)建線程的操作。在Runnable接口中,只包含一個抽象的run()方法。

publicinterfaceRunnable{

publicabstractvoidrun()

}利用Runnable接口創(chuàng)建線程,須首先定義一個實現(xiàn)Runnable接口的類,在該類中必須定義run()方法的實現(xiàn)代碼。

classMyRunnableimplementsRunnable

{

publicvoidrun()

{

//新建線程上執(zhí)行的代碼

}

}直接創(chuàng)建實現(xiàn)了Runnable接口的類的對象并不能生成線程對象,還必須定義一個Thread對象,通過使用Thread類的構造函數(shù)去新建一個線程,并將實現(xiàn)Runnable接口的類的對象引用,作為參數(shù)傳遞給Thread類的構造函數(shù),最后通過start()方法來啟動新建線程?;静襟E如下:

MyRunnabler=newMyRunnable();

Threadt=newThread(r);

t.start;

我們將例11-2改寫為通過實現(xiàn)Runnable接口來創(chuàng)建線程,代碼如例11-3所示。例11-3RunnerDemo.java

1classMyRunnerimplementsRunnable{

2publicvoidrun(){

3Strings=Thread.currentThread().getName();

4for(inti=1;i<=10;i++)

5System.out.println(s+":"+i);

6}

7}

8publicclassRunnerDemo{

9publicstaticvoidmain(String[]args){

10MyRunnerr1=newMyRunner();11Threadt1=newThread(r1,"線程1");

12Threadt2=newThread(r1,"線程2");

13t1.start();

14t2.start();

15for(inti=1;i<=10;i++)

16System.out.println("main主線程"+":"+i);

17}

18}11.2.2線程的管理

1.線程的狀態(tài)

線程在它的生命周期中一般具有五種狀態(tài),即新建、就緒、運行、堵塞和死亡。線程的狀態(tài)轉(zhuǎn)換圖如圖11-5所示。圖11-5線程的狀態(tài)轉(zhuǎn)換

1)新建狀態(tài)(newThread)

在程序中用構造函數(shù)創(chuàng)建了一個線程對象后,新生的線程對象便處于新建狀態(tài)。此時,該線程僅僅是一個空的線程對象,系統(tǒng)不為它分配相應資源,并且它還處于不可運行狀態(tài)。

2)就緒狀態(tài)(Runnable)

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

3)運行狀態(tài)(Running)

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

4)阻塞狀態(tài)(Blocked)

一個正在執(zhí)行的線程在某些特殊情況下,放棄CPU而暫時停止運行,如被人為掛起或需要執(zhí)行費時的輸入、輸出操作時,將讓出CPU并暫時中止自己的執(zhí)行,進入阻塞狀態(tài)。在運行狀態(tài)下,如果調(diào)用sleep()、suspend()、wait()等方法,線程將進入阻塞狀態(tài)。阻塞狀態(tài)中的線程,Java虛擬機不會為其分配CPU,直到引起堵塞的原因被消除后,線程才可以轉(zhuǎn)入就緒狀態(tài),從而有機會轉(zhuǎn)到運行狀態(tài)。

5)死亡狀態(tài)(Dead)

線程調(diào)用stop()方法時或run()方法執(zhí)行結束后,線程即處于死亡狀態(tài),結束了生命周期。處于死亡狀態(tài)的線程不具有繼續(xù)運行的能力。

2.線程的優(yōu)先級

在多線程的執(zhí)行狀態(tài)下,我們并不希望按照系統(tǒng)隨機分配時間片方式給一個線程分配時間。因為隨機性將導致程序運行結果的隨機性。因此,在Java中提供了一個線程調(diào)度器來監(jiān)控程序中啟動后進入可運行狀態(tài)的所有線程。線程調(diào)度器按照線程的優(yōu)先級決定調(diào)度哪些線程來執(zhí)行,具有高優(yōu)先級的線程會在較低優(yōu)先級的線程之前得到執(zhí)行。同時,線程的調(diào)度是搶先式的,即如果當前線程在執(zhí)行過程中,一個具有更高優(yōu)先級的線程進入可執(zhí)行狀態(tài),則該高優(yōu)先級的線程會被立即調(diào)度執(zhí)行。在Java中,線程的優(yōu)先級是用整數(shù)表示的,取值范圍是1~10。Thread類中與優(yōu)先級相關的三個靜態(tài)常量如下:

■低優(yōu)先級:Thread.MIN_PRIORITY,取值為1。

■缺省優(yōu)先級:Thread.NORM_PRIORITY,取值為5。

■高優(yōu)先級:Thread.MAX_PRIORITY,取值為10。

線程被創(chuàng)建后,其缺省的優(yōu)先級是缺省優(yōu)先級Thread.NORM_PRIORITY??梢杂梅椒╥ntgetPriority()來獲得線程的優(yōu)先級,同時也可以用方法voidsetPriority(intp)在線程被創(chuàng)建后改變線程的優(yōu)先級。

3.線程的調(diào)度

在實際應用中,一般不提倡依靠線程優(yōu)先級來控制線程的狀態(tài),Thread類中提供的關于線程調(diào)度控制的方法如表11-2所示。使用這些方法可將運行中的線程狀態(tài)設置為阻塞或就緒,從而控制線程的執(zhí)進。表11-2線程調(diào)度控制的常用方法

1)線程的睡眠(sleep)

線程的睡眠是指運行中的線程暫時放棄CPU,轉(zhuǎn)到阻塞狀態(tài)。通過調(diào)用Thread類的sleep()方法可以使線程在規(guī)定的時間內(nèi)睡眠,在設置的時間內(nèi)線程會自動醒來,這樣便可暫緩線程的運行。線程在睡眠時若被中斷將會拋出一個InterruptedException異常,因此在使用sleep()方法時必須捕獲InterruptedException異常。

在例11-4中,利用線程的sleep()方法實現(xiàn)了每隔1秒輸出0~9十個整數(shù)。例11-4SleepDemo.java

1classSleepDemoextendsThread{

2publicvoidrun(){

3 for(inti=0;i<10;i++){

4 System.out.println(i);

5 try{

6 sleep(1000);

7 }catch(InterruptedExceptione){}

8}

9}

10publicstaticvoidmain(Stringargs[]){11SleepDemot=newSleepDemo();

12t.start();

13}

14}

2)線程的讓步(yield)

與sleep()方法相似,通過調(diào)用Thread類提供的yield()方法可暫停當前運行中的線程,使之轉(zhuǎn)入就緒狀態(tài),但是不能由用戶指定線程暫停時間的長短。同時,它把執(zhí)行的機會轉(zhuǎn)給具有相同優(yōu)先級別的線程,如果沒有其他的相同優(yōu)先級別的可運行線程,則yield()方法不做任何操作。sleep()方法和yield()方法都可使處于運行狀態(tài)的線程放棄CPU,兩者的區(qū)別如下:

■sleep()是將CPU出讓給其他任何線程,而yield()方法只會給優(yōu)先級更高或同優(yōu)先級的線程運行的機會?!鰏leep()方法使當前運行的線程轉(zhuǎn)到阻塞狀態(tài),在指定的時間內(nèi)肯定不會執(zhí)行;而yield()方法將使運行的線程進入就緒狀態(tài),所以執(zhí)行yield()的線程有可能在進入到就緒狀態(tài)后馬上又被執(zhí)行。

例11-5YieldDemo.java

1publicclassYieldDemo{

2publicstaticvoidmain(Stringargs[]){

3MyThreadt1=newMyThread("t1");

4MyThreadt2=newMyThread("t2");

5t1.start();

6t2.start();7}

8}

9classMyThreadextendsThread{

10MyThread(Strings){

11super(s);

12}

13publicvoidrun(){

14for(inti=0;i<100;i++){

15System.out.println(getName()+":"+i);

16if(i%10==0)

17yield(); 18}

19}

20}

21

程序運行結果如圖11-6所示。

在例11-5的輸出結果中,每個線程輸出到10的倍數(shù)時,由于使用yield()語句,則下一個顯示一定切換到其他線程。如果不用yield,則顯示結果是隨機的。圖11-6線程的讓步

3)線程的掛起與恢復(suspend與resume)

通過調(diào)用Thread類提供的suspend()方法可暫停正在運行的線程,使其進入阻塞狀態(tài);可通過resume()方法恢復。

4.線程的同步

在之前編寫的多線程程序中,多個線程通常是獨立運行的,各個線程具有自己的獨占資源,而且異步執(zhí)行。也即每個線程都包含了運行時自己所需要的數(shù)據(jù)或方法,而不必去關心其他線程的狀態(tài)和行為。但是在有些情況下,多個線程需要共享同一資源,如果此時不去考慮線程之間的協(xié)調(diào)性,就可能造成運行結果的錯誤。例如,在銀行對同一個賬戶存錢,一方存入相應金額后,賬戶還未修改賬戶余額時,另一方也把一定金額存入該賬戶,因此可能導致所返回的賬戶余額不正確。例11-6模擬了丈夫和妻子分別對一張銀行卡存款的過程。例11-6ATMDemo1.java

1classATMDemo1{

2publicstaticvoidmain(String[]args){

3BankAccountvisacard=newBankAccount();

4ATM丈夫=newATM("丈夫",visacard,200);

5ATM妻子=newATM("妻子",visacard,300);

6 ?Threadt1=newThread(丈夫);

7 Threadt2=newThread(妻子);

8 System.out.println("當前賬戶余額為:"+visacard.getmoney());

9 t1.start(); 10 t2.start();

11}

12}

13Cla1ssATMimplementsRunnable{//模擬ATM機或柜臺存錢

14BankAccountcard;

15Stringname;

16longm;

17ATM(Stringn,BankAccountcard,longm){

18 =n;

19this.card=card; 20 this.m=m;

21}

22publicvoidrun(){

23 card.save(name,m); //調(diào)用方法存錢

24 System.out.println(name+"存入"+m+"后,賬戶余額為"+card.getmoney());

25}

26}

27classBankAccount{

28staticlongmoney=1000; //設置賬戶中的初始金額 29publicvoidsave(Strings,longm){ //存錢

30System.out.println(s+“存入”+m);

31 longtmpe=money; //獲得當前賬戶余額

32try{ //模擬存錢所花費的時間

33 Thread.currentThread().sleep(10);

34 }catch(InterruptedExceptione) {}

35 money=tmpe+m; //相加之后存回賬戶

36 }

37publiclonggetmoney(){ //獲得當前賬戶余額 38 returnmoney;

39}

40}

程序運行結果如圖11-7所示。圖11-7模擬ATM存款的過程在這個存款程序中,賬戶的初始余額為1000元,丈夫存入200元后,存款為1200元,而妻子存入300元后,賬戶余額理論上應該為1500,但是結果卻顯示為1300元。

這個結果和實際不符,問題就出在當線程t1存錢后,通過程序第31行語句獲得當前賬戶余額1000后,立即sleep(10),因此在還來不及對賬戶余額進行修改時,線程t2執(zhí)行存錢操作,也通過程序第31行語句獲得當前賬戶余額。由于線程1未修改余額的值,因此線程2獲得的余額仍為1000。最后,當線程1和線程2分別繼續(xù)執(zhí)行時,均在各自獲得的余額數(shù)目基礎上加上存入的金額數(shù)。上例出錯的原因就在于,在線程t1的執(zhí)行尚未結束時,money被線程t2讀取。在Java中,為了保證多個線程對共享資源操作的一致性和完整性,引入了同步機制。所謂線程同步,即某個線程在一個完整操作的全執(zhí)行過程中,獨享相關資源使其不被侵占,從而避免了多個線程在某段時間內(nèi)對同一資源的訪問。

Java中可以通過對關鍵代碼段使用關鍵字synchronized來表明被同步的資源,也即給資源加“鎖”,這個鎖稱之為互斥鎖。當某個資源被synchronized關鍵字修飾時,系統(tǒng)在運行時會分配給它一個互斥鎖,表明該資源在同一時刻只能被一個線程訪問。

實現(xiàn)同步的方法有兩種,即利用同步方法實現(xiàn)同步和利用同步代碼塊實現(xiàn)同步。

1)利用同步方法來實現(xiàn)同步

只需要將關鍵字synchronized放置于方法前修飾該方法即可。同步方法是利用互斥鎖保證關鍵字synchronized所修飾的方法在被一個線程調(diào)用時,其他試圖調(diào)用同一實例中該方法的線程都必須等待,直到該方法被調(diào)用結束釋放后,互斥鎖被分給下一個等待的線程。

我們對例11-6進行一些改動,將synchronized放置在publicvoidsave(Strings,longm)方法之前,即:

publicsynchronizedvoidsave(Strings,longm)

則程序的運行結果如圖11-8所示。圖11-8同步機制下的模擬ATM存款過程

2)利用同步代碼塊來實現(xiàn)同步

為了實現(xiàn)線程的同步,我們也可以將對共享資源操作的代碼塊放入一個同步代碼塊中。同步代碼塊的語法形式如下:

返回類型方法名(形參數(shù)數(shù))

{

synchronized(Object)

{

//關鍵代碼

}

}同步代碼塊的方法也是利用互斥鎖來實現(xiàn)對共享資源的有序操作,其中Object是需要同步的對象的引用。我們利用同步代碼塊對例11-6進行修改,運行結果如圖11-8所示。publicvoidsave(Strings,longm){

synchronized(this){

System.out.println(s+"存入"+m);

longtmpe=money;

try{

Thread.currentThread().sleep(10);

}catch(InterruptedExceptione) {}

money=tmpe+m;

}

} 11.3任務實施

我們將考試系統(tǒng)中的倒計時功能從原考試系統(tǒng)分離,并做了部分修改,將其完善成為一個獨立的應用程序。如圖11-9~圖11-11所示,當點擊【開始考試】按鈕后,計時系統(tǒng)開始運作。期間可以點擊【暫??荚嚒亢汀纠^續(xù)考試】按鈕,使計時系統(tǒng)在暫停和繼續(xù)考試之間進行切換。當考試時間結束時,將彈出對話框提示,按【確定】按鈕可退出系統(tǒng)。圖11-9倒計時開始計時圖11-10倒計時暫停計時圖11-11倒計時結束例11-7TestClock.java

1importjava.text.NumberFormat;

2importjava.awt.event.*;

3importjavax.swing.*;

4publicclassTestClockimplementsActionListener{

5JFramejf;

6JButtonbegin;

7JButtonend;

8JButtonpause;

9JPanelp1;

10JLabelclock;11ClockDispalymt;

12publicTestClock(){

13 f=newJFrame("倒計時");

14 begin=newJButton("開始考試");

15 end=newJButton("結束考試");

16 pause=newJButton("暫停考試");

17 p1=newJPanel();

18 JLabelclock=newJLabel();

19 clock.setHorizontalAlignment(JLabel.CENTER);

20 p1.add(begin);

21 p1.add(pause); 22 p1.add(end);

23 jf.add(p1,“North”);

24 jf.add(clock,“Center”);

25 jf.setSize(340,180);

26 jf.setLocation(500,300);

27 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

28 jf.setVisible(true);

29mt=newClockDispaly(clock,100); //設置考試時間為100分鐘

30begin.addActionListener(this);31 pause.addActionListener(this);

32 end.addActionListener(this);

33}

34publicstaticvoidmain(String[]args){

35 TestClocktest=newTestClock();

36}

37publicvoidactionPerformed(ActionEvente){

38 Strings=e.getActionCommand();

39 if(s.equals("開始考試")){

40 begin.setEnabled(false); 41 mt.start(); //啟動倒計時線程

42}

43 elseif(s.equals("暫??荚?)){

44 pause.setText("繼續(xù)考試");

45 mt.suspend();

46 }

47 elseif(s.equals("繼續(xù)考試")){

48 pause.setText("暫??荚?);

49 mt.resume();

50 }51elseif(s.equals("結束考試")){

52begin.setEnabled(false);

53pause.setEnabled(false);

54end.setEnabled(false);

55

溫馨提示

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

評論

0/150

提交評論