




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第十三章多線程遼寧機電職業技術學院信息工程系軟件教研室遲勇回顧補充代碼完成:使用字符流從指定文件中讀取數據 FileReaderfr=newFileReader("C:\\myproject\\document\\readme1.txt"); charc[]=newchar[260];//定義了字符型數組作為緩沖區while(fr.
(c)!=-1){//從流fr中讀入字符到數組c中 Stringstr=newString(
); System.out.println(“正在寫出到控制臺:”+str);
}
fr.close();
cread本章學習任務使用線程改造時鐘程序利用多線程同步技術提取賬戶資金設置線程優先級,模擬乘客登車本章技能目標了解Java多線程機制的意義學會創建線程的兩種基本方法了解線程的生命周期掌握線程的同步和調度技巧預習檢查(1)--預習新單詞單詞釋義單詞釋義ProcessThreadMulti-ThreadPoolRunnablesleepjoinSynchronizednotify預習檢查(2)實現多線程的兩種方式:繼承java.lang.Thread超類、實現java.lang.Runnable接口;多線程-內容目錄線程概述多線程的實現線程的同步與調度13.1線程的概述每個應用程序都是系統的一個進程(Process),它們擁有自己獨立內存空間和系統資源,互不干擾。它們的數據互不關聯、彼此獨立,只是通過分配的進程ID號(PID)來識別。線程(Thread)與進程類似,是程序中一個具有獨立順序的程序流,它有自己的開始、代碼體和結束。一個進程可以包含若干個線程。但與進程不同的是,線程之間可以共享內存空間和資源,線程間切換的系統開銷遠小于進程,所以有時我們也把線程稱為輕進程(Light-weightProcess)。13.1線程的概述表13-1系統隊列中的進程與線程……
進程2線程1線程2數據資源
進程1線程1線程2數據資源單線程,就是說一個應用進程中只包含一個線程(即主線程),此線程運行結束則進程也會結束。多線程(Multi-Thread)就是在操作多任務的思想基礎上,將一個進程再分解為若干個線程,從而使單個進程也可以同時完成多種任務。單線程與多線程模式對比圖13-2單線程運行時序圖CPU運行時刻T線程2線程1線程阻塞執行請求1請求2處理1下載數據1處理2下載數據2圖13-2單線程運行時序圖CPU運行時刻T線程2線程1線程阻塞執行請求1請求2處理1下載數據1處理2下載數據2多線程的比喻再做一個比較容易理解的比喻:畢業酒會上,同學們圍坐在一個圓桌子旁,每人面前有一個大酒杯,并且有一個服務生繞著桌子專門為大家斟滿酒,因為杯子很大,同學們的酒量又不行,服務生已經繞著桌子轉了一圈,大家面前的酒還沒喝完,但是熱心的服務生會再次為大家斟滿酒,這樣周而復始,在桌子旁的每個人可能都感覺自己酒杯里的酒始終沒有喝完,而這其實都是那位熱心的服務生的功勞。我們假設每個人喝酒的動作都是一個線程,那么服務生就是高效率的CPU,而他手里的酒就是要下載或處理的資源。多線程-內容目錄線程概述多線程的實現線程的同步與調度13.2多線程(Multi-Thread)的實現1多線程的改造示例2構造多線程的方式3線程的常用方法4線程的生命周期13.2.1多線程的改造實例在第11章我們曾經接觸過線程的例子,借助線程實現了一個動畫的繪制,并且介紹了Thread類的靜態方法sleep(),它可使當前線程休眠,具體請參考前面示例11-11。下面先來看一個簡單的例子,借此了解使用多線程的必要意義。我們希望在Applet中顯示一個時鐘,并且能顯示每一秒鐘的變化,它的代碼如下:例13-1:Applet時鐘程序DigitalClock.htm程序代碼<HTML><BODYBGCOLOR="000000"><CENTER><APPLET> code ="DigitalClock.class" width ="500" height ="100"</APPLET></CENTER></BODY></HTML>例13-1:Applet時鐘程序importjava.awt.Graphics;importjava.awt.Font;importjava.util.Date;publicclassDigitalClockextendsjava.applet.Applet{ FonttheFont=newFont("TimesRoman",Font.BOLD,24);//定義字體
DatetheDate=newDate();//初始化Date實例為當前日期
DigitalClock.java
程序代碼publicvoidstart() { while(true) { theDate=newDate(); repaint();//發出重繪請求
try{ Thread.sleep(1000);//線程休眠1秒種
}catch(InterruptedExceptione){} }} publicvoidpaint(Graphicsg) { g.setFont(theFont);//設置字體格式
g.drawString(theDate.toString(),10,50);//繪制當前日期字符串
}}例13-1:Applet時鐘程序運行出現的問題代碼的運行結果:這個時鐘并沒有正常地運轉起來,這是為什么呢?時鐘沒有正常運轉的原因是在于:無限循環while(true)。這個程序并沒有應用多線程,整個Applet只有一個主線程,并且被while(true)獨占,它占據了這個程序的所有CPU時間和資源。下面我們改造這個程序,讓它支持多線程,如示例13-2所示。DigitalClock.java程序代碼例13-2:改造后的Applet時鐘程序importjava.awt.Graphics;importjava.awt.Font;importjava.util.Date;/*程序實現了Runnable接口,來引入多線程功能*/publicclassDigitalClockextendsjava.applet.AppletimplementsRunnable{ FonttheFont=newFont("TimesRoman",Font.BOLD,24); DatetheDate=newDate(); Threadrunner;//定義了線程類實例
例13-2:改造后的Applet時鐘程序--2
publicvoidstart() { if(runner==null)//判斷線程是否為空
{ runner=newThread(this);//初始化線程類實例
runner.start();//線程類實例進入就緒狀態
} } publicvoidstop() { if(runner!=null) { runner.stop();//終止線程
runner=null;//釋放線程所占內存空間
} }
publicvoidrun()//實現Runnable接口的run()方法
{ while(true) { theDate=newDate(); repaint(); try{ Thread.sleep(1000);} catch(InterruptedExceptione){} } } publicvoidpaint(Graphicsg) { g.setFont(theFont); g.drawString(theDate.toString(),10,50); }}運行結果【運行結果】運行這個Applet程序,發現時鐘可以正常運轉,如圖所示。13.2.2構造多線程的方式Thread類的構造方法如下:Thread([ThreadGroupgroup],[Runnabletarget],[Stringname]); group參數指定該線程所屬的線程組;target作為其運行對象,它必須實現Runnable接口;name為線程名稱。三個參數為可選,當某個參數為null時,構造方法可以為:Thread();Thread(Runnabletarget);Thread(Runnabletarget,Stringname);Thread(Stringname);Thread(ThreadGroupgroup,Runnabletarget);Thread(ThreadGroupgroup,Stringname);構造多線程的方式Runnable接口中有run()方法,Thread類也有run()方法,它是線程運行的主體,線程運行所需的代碼寫在此方法體內,所以應該或必須重寫run()方法來完成所需功能。下面介紹創建線程的兩種方法:繼承Thread超類和實現Runnable接口。方式1:繼承Thread類定義的線程類可以通過繼承java.lang.Thread超類來實現,語法如下:
publicclass<類名>extendsThread{……}例13-3:多線程顯示輸出TwoThread.java程序代碼publicclassTwoThread{ staticThread01th01;//定義線程類Thread01實例th01 staticThread02th02;//定義線程類Thread02實例th02 publicstaticvoidmain(Stringargs[]){ th01=newThread01("thisisthread01!");//創建實例th01 th02=newThread02("Iamthread02!");//創建實例th02 th01.start();//實例th01進入就緒狀態
th02.start();//實例th02進入就緒狀態
}}方式1:繼承Thread類--2classThread01extendsThread{ publicThread01(Stringname){ super(name);}//為線程命名 publicvoidrun()//重寫線程體
{ for(inti=0;i<5;i++) { System.out.println(this.getName());//輸出線程名
try{Thread.sleep(500);}
catch(InterruptedExceptione){} } }}classThread02extendsThread{ publicThread02(Stringname){ super(name);}//為線程命名
publicvoidrun()//重寫線程體
{ for(inti=0;i<5;i++)
{ System.out.println(this.getName());//輸出線程名
try{Thread.sleep(300);}//線程睡眠,讓出CPU catch(InterruptedExceptione){} } } }【運行結果】【運行結果】運行結果如圖所示,兩個線程實例名交互輸出。方式2:實現Runnable接口定義的線程類在構造可以通過一個實現了java.lang.Runnable接口的目標對象來實現,語法如下:
publicclass<類名>extends<父類名>implementsRunnable
{publicvoidrun(){……}//重寫Runnable接口中的run()方法
}
使用Runnable接口來構造線程的優點顯而易見:Java不支持多繼承,為了繼承父類,只能采取實現接口的方式來構造線程。
例如前面的Applet示例13-2。需要注意的是使用這種方式構建線程時,必須指明線程的目標對象target,即指明線程的主體run()方法的定義位置,其程序結構大體如下:
實現Runnable接口的程序結構publicclassDigitalClockextendsAppletimplementsRunnable{ Threadrunner;//定義線程類實例
publicvoidstart() { if(runner==null) { runner=newThread(this);//初始化線程類實例,并指明target對象this runner.start();//線程就緒,獲得CPU運行調度后,將開始運行run()方法
} } publicvoidrun(){……}//run()方法定義在實現Runnable接口的類中}13.2.3線程的常用成員方法下面是線程操作中常用的一些方法,包括前面使用過的start()、sleep()、run()等方法;下一小節將要介紹的線程同步和調度join()、yield()等方法;還有用于設置或獲取線程屬性的getName()、getPriority()等方法。線程生命周期相關方法voidstart()使該線程就緒。當CPU開始執行該線程時將調用run方法。voidrun()該線程運行的主體。voidstop()已過時。用于退出線程,該方法具有不安全性。退出目標線程應采取判斷線程對象是否改變并修改線程變量為null的方式進行。13.2.3線程的常用成員方法線程屬性相關方法booleanisAlive()測試線程是否處于活動狀態。booleanisDaemon()測試該線程是否為后臺線程。staticThread
currentThread()返回對當前正在執行的線程對象的引用。String
getName()返回該線程的名稱。intgetPriority()返回線程的優先級。Thread.State
getState()返回該線程的狀態。ThreadGroup
getThreadGroup()返回該線程所屬的線程組。staticbooleaninterrupted()測試當前線程是否已經中斷。voidsetDaemon(booleanon)將該線程標記為后臺線程。voidsetName(Stringname)改變線程名為參數name。voidsetPriority(intnewPriority)更改線程的優先級。String
toString()返回該線程的字符串表示形式。13.2.3線程的常用成員方法調度與線程同步方法voiddestroy()已過時。該方法用于銷毀線程,可能會出現死鎖。voidresume()已過時。該方法只與suspend()一起使用,用于恢復掛起的線程,有可能造成死鎖。staticvoidsleep(longmillis)在指定的longmillis毫秒數內讓當前正在執行的線程休眠(暫停執行)。voidinterrupt()中斷線程。voidjoin()等待該線程終止。voidjoin(longmillis)等待該線程終止的時間最長為longmillis毫秒。voidsuspend()已過時。將目標線程掛起,可能會出現死鎖。staticvoidyield()暫停當前正在執行的線程對象,并執行其他線程。13.2.4線程的生命周期在Java程序中,線程就是一段Java代碼,就是類的對象。同其它對象(比如Applet)一樣,它也有從新生到消亡的生命周期,線程的生命周期有5種狀態:新生、就緒、運行、阻塞、死亡,如圖13-4所示。新生就緒運行死亡newstart()run()stop()阻塞sleep()、wait()yield()表13-4線程的生命周期多線程-內容目錄線程概述多線程的實現線程的同步與調度13.3線程的同步和調度1一個失敗的多線程示例2線程同步(synchronized)3有關線程的調度方法13.3.1 一個失敗的多線程示例在多線程的程序中,由于多個線程可以同時訪問同一個變量或方法,如果一個線程對它們的訪問尚未結束,另一個線程就開始處理,就會產生錯誤的結果。例如,現銀行有一賬戶,假設有多個客戶在不同的取款臺同時對這一賬戶取款,前面的客戶提款操作尚未完成,而后面的客戶就開始提款操作,可能此時后面的客戶是在前面客戶尚未更新的賬戶余額上操作,那就會造成透支。請先看下面的失敗案例。例13-4:利用多線程模擬多個客戶同時提取同一賬戶資金的情況銀行賬號類:BankAccount提款客戶類:Customer銀行系統主類:BankSystem運行結果會發現結果運行不正確!顯示結果第五行:“*****客戶乙提款后:余額為4500元*****”,實際應為4000元!而最后余額實際應為3000元?!具\行結果】按照順序編譯,運行這三個類文件,結果如圖所示:【程序分析】解決這種問題的方法解決這種問題的方法應當采用多線程的“同步機制”。所謂“同步”(synchronized),是指將被當前線程訪問的對象或方法加鎖(即獨占資源,其他想訪問此資源的線程只有等待),只有取得開鎖鑰匙的線程才能訪問該對象或方法。在例13-4中,takeMoney()方法將被客戶甲和客戶乙兩個線程訪問,如果能限制在客戶甲訪問該方法時,客戶乙不能訪問該方法,直到客戶甲退出takeMoney()方法,客戶乙才能訪問takeMoney()方法,這樣,就可以保證賬戶余額的正確更新。13.3.2 線程同步(synchronized)實現同步訪問某一對象或方法,必須在被訪問的對象或方法前加synchronized關鍵字。如果對象或方法被設為“同步”,在同一時刻將只能有一個線程操作此對象或方法(相當于在要操作的對象和方法上加了鎖)。
如果將示例13-4的takeMoney()加上同步控制符synchronized(見代碼第4行,其它代碼不變),將會得到正確的結果。見示例13-5。
例13-5:利用多線程同步技術提取賬戶資金的情況publicsynchronizedstaticvoidtakeMoney(Stringclient,intiAmount)//同步支取資金方法,此處添加了synchronized關鍵字13.3.3線程的調度有時一些線程需要共享一些數據資源,例如典型的“生產者-消費者”的例子,生產者向倉庫輸入一份商品,消費者從倉庫取出一份商品,生產者必須預先將一份商品放入倉庫,否則消費者無法從空倉庫取出商品。再比如兩個線程同時操作一個網絡文件,一個將數據寫入文件,一個從文件中讀取數據,要求在數據完全寫入文件后才能讀取,這些情況下,要考慮這些線程的運行狀態,即在一定條件下線程才可以切換,這需要“調度”來達到預期效果。調度的方法調度的方法通常有兩種:1)設置線程優先級完成自動調度;2)使用等待(wait)、喚醒(notify)等方法完成手工調度。1.線程優先級(Priority)在多線程機制下,在線程等待池中的哪個線程能夠被優先執行,取決于線程的“優先級”,優先級高的線程優先獲得CPU運行權。Java中線程的優先級分為1~10級,1級(即MIN_PRIORITY)最低,10級(即MAX_PRIORITY)最高。如果不做優先級設置,通常線程的默認優先級為5級(即NORM_PRIORITY),如果要改變線程優先級,可以使用setPriority()方法設置。例13-6:設置線程優先級,模擬乘客登車例13-6:設置線程優先級,模擬乘客登車(假設乘客中有兒童、婦女和老人,其中,兒童優先上車,老年人次之,最后是婦女,每個站點上車的人數是隨機的)/*子線程類*/classPassengerextendsThread{publicPassenger(StringpassengerName){super(passengerName);//設置線程名稱}publicvoidrun(){intsum=(int)(Math.random()*5);//隨機生成站點人數System.out.println("===共有"+sum+"個"+this.getName()+"正在等待上車.===");for(inti=0;i<sum;i++){System.out.println("現在第"+(i+1)+"個"+this.getName()+"正在上車.");//顯示上車乘客的身份}}publicclassGetOnBus{publicstaticvoidmain(String[]args){Passengerchildren=newPassenger("學齡前兒童");Passengerwomen=newPassenger("婦女");Passengerelder=newPassenger("老年人");children.setPriority(10); //設置children為最高優先級elder.setPriority(Thread.NORM_PRIORITY);//設置elder為普通優先級women.setPriority(1); //設置women為最低優先級women.start(); //線程就緒elder.start(); children.start(); }}運行結果但是這種調度,不一定按照我們設想的順序完成,因為線程執行的先后順序是由操作系統對CPU的調度機制來控制的,即使給線程設置高優先級,也不能確定它一定會得到CPU的執行時間!2.手工調度由于自動調度的局限,使得略為復雜的線程控制就無法實現,如果我們希望線程能按照我們的預期那樣地運行,就必須使用wait()與notify()等方法來手工調度。wait()與notify()、notifyAll()等方法被定義在Object類中:wait()方法可使當前線程處于等待(阻塞)狀態,從而釋放自己的線程鎖給其它線程使用,直到其它線程使用notify()或notifyAll()方法喚醒它為止。notifyAll()方法可喚醒所有調用了wait()方法而處于等待的線程,而notify()方法可喚醒單個線程,但這種喚醒是隨意性的。(補充實例13-7:)例13-7使用手工調度改造前面用戶提款繼續改造前面用戶提款的實例(例13-4、例13-5),使用戶提款的順序可以按照我們設定的過程來執行。改造過程如下:(1)為Customer類添加一個公用的BankAccount類對象,并改造其構造方法;(2)為BankAccount類的takeMoney()方法添加wait()和notify()方法,實現手工調度。例13-7使用手工調度改造前面用戶提款銀行賬號類:BankAccountclassBankAccount{ privatestaticinttotalBalance=1000;//假設賬戶余額為1000元 publicsynchronizedvoidtakeMoney(Stringclient,intiAmount)//支取資金方法 { intgetBalance=totalBalance;//取得賬戶余額 getBalance-=iAmount;//根據支取現金的數量,修正賬戶余額 /*利用sleep(1000)休眠1秒鐘,模擬提款時數據庫更新需要的時間*/ try{ Thread.sleep(1000); }catch(InterruptedExceptione){ } totalBalance=getBalance;//提款后,更新賬戶余額 System.out.println("*****"+client+"提款后:"+"余額為"+totalBalance+"元"+"*****");
notify();//通知處于等待狀態的線程,可以啟動
try{
wait();//使當前線程處于等待狀態
}catch(Exceptione){ } } publicstaticintgetBalance()//讀取總余額 { returntotalBalance; }}例13-7使用手工調度改造前面用戶提款提款客戶類:CustomerclassCustomerextendsThread//客戶線程{
BankAccountaccount;//用戶要操作的公用對象 privateintgetMoney;//客戶提款額 publicCustomer(StringcustomerName,intmoney,BankAccountaccount)//構造方法 { super(customerName); getMoney=money;
this.account=account; } publicvoidrun()//客戶線程主體 { for(in
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 材料力學與智能材料性能研究拓展重點基礎知識點
- 行政法學精英訓練試題及答案
- 行政法學復習資料的使用與反饋:試題及答案
- 時空組學 數據集格式規范 征求意見稿
- 行政管理應用能力試題與答案
- 火災人亡后續應急預案(3篇)
- 小學生遇到火災應急預案(3篇)
- 法學概論考試的內容適應性研究試題及答案
- 2025年網絡管理員考試心得及試題與答案
- 2025年軟考設計師考試經歷分享及試題與答案
- 質量管理小組活動準則TCAQ10201-2020
- GB/T 43293-2022鞋號
- YC/T 215-2007煙草行業聯運通用平托盤
- JJF 1751-2019菌落計數器校準規范
- GB/T 40805-2021鑄鋼件交貨驗收通用技術條件
- 中考歷史-世界近現代國際關系復習課件
- 報價單模板及范文(通用十二篇)
- 五年級異分母分數加減法第一課時課件
- 幼兒繪本故事:什么都行的哈力船長
- 高考減壓講座通用PPT課件
- 高考考前指導(班主任)心理方面、應試復習方面等
評論
0/150
提交評論