Java語言程序設(shè)計(jì)(自考)課件 第十章 多線程_第1頁
Java語言程序設(shè)計(jì)(自考)課件 第十章 多線程_第2頁
Java語言程序設(shè)計(jì)(自考)課件 第十章 多線程_第3頁
Java語言程序設(shè)計(jì)(自考)課件 第十章 多線程_第4頁
Java語言程序設(shè)計(jì)(自考)課件 第十章 多線程_第5頁
已閱讀5頁,還剩54頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第十章多線程

學(xué)習(xí)目標(biāo)能夠敘述線程和多線程的概念,線程各種狀態(tài)之間轉(zhuǎn)換的條件,線程的優(yōu)先級能夠使用Thread類和Runnabe接口創(chuàng)建線程能夠?qū)€程進(jìn)行控制,實(shí)現(xiàn)線程之間的互斥和同步本章主要內(nèi)容線程和多線程12線程的基本控制3創(chuàng)建線程線程的互斥4線程的同步5線程的概念程序是一段靜態(tài)的代碼,它是應(yīng)用程序執(zhí)行的藍(lán)本一個進(jìn)程既包括其所要執(zhí)行的指令,也包括了執(zhí)行指令所需的任何系統(tǒng)資源,如CPU、內(nèi)存空間、I/O端口等,不同進(jìn)程所占用的系統(tǒng)資源相對獨(dú)立線程是進(jìn)程執(zhí)行過程中產(chǎn)生的多條執(zhí)行線索,是比進(jìn)程單位更小的執(zhí)行單位線程的結(jié)構(gòu)在Java中,線程由3部分組成:虛擬CPU,封裝在java.lang.Thread類中,它控制著整個線程的運(yùn)行執(zhí)行的代碼,傳遞給Thread類,由Thread類控制按序執(zhí)行處理的數(shù)據(jù),傳遞給Thread類,是在代碼執(zhí)行過程中所要處理的數(shù)據(jù)當(dāng)一個線程被構(gòu)造時,它由構(gòu)造方法參數(shù)、執(zhí)行代碼、操作數(shù)據(jù)來初始化一個線程所執(zhí)行的代碼與其他線程可以相同也可以不同一個線程訪問的數(shù)據(jù)與其他線程可以相同也可以不同多線程的優(yōu)勢使用多線程可以在線程間直接共享數(shù)據(jù)和資源,而多進(jìn)程之間不能做到這一點(diǎn)使用多線程適合于開發(fā)有多種交互接口的程序多線程的機(jī)制可以減輕編寫交互頻繁、涉及面多的程序的困難,如偵聽網(wǎng)絡(luò)端口的程序線程的狀態(tài)線程一共有4種狀態(tài),分別是新建、可運(yùn)行狀態(tài)、死亡及阻塞新建線程對象剛剛創(chuàng)建,還沒有啟動,此時還處于不可運(yùn)行狀態(tài)此時剛創(chuàng)建的線程處于新建狀態(tài),但已有了相應(yīng)的內(nèi)存空間以及其它資源可運(yùn)行狀態(tài)此時的線程已經(jīng)啟動,線程可能正在運(yùn)行,也可能沒有運(yùn)行,只要CPU一空閑,馬上就會運(yùn)行可以運(yùn)行但沒在運(yùn)行的線程都排在一個隊(duì)列中,這個隊(duì)列稱為就緒隊(duì)列可運(yùn)行狀態(tài)中,正在運(yùn)行的線程處于運(yùn)行狀態(tài),等待運(yùn)行的線程處于就緒狀態(tài)一般地,單CPU情況下,最多只有一個線程處于運(yùn)行狀態(tài),可能會有多個線程處于就緒狀態(tài)調(diào)用線程的start()方法可使線程處于“可運(yùn)行”狀態(tài)死亡線程死亡的原因有兩個一是run()方法中最后一個語句執(zhí)行完畢二是當(dāng)線程遇到異常退出時便進(jìn)入了死亡狀態(tài)阻塞一個正在執(zhí)行的線程因特殊原因,被暫停執(zhí)行,就進(jìn)入阻塞狀態(tài)阻塞時線程不能進(jìn)入就緒隊(duì)列排隊(duì),必須等到引起阻塞的原因消除,才可重新進(jìn)入隊(duì)列排隊(duì)引起阻塞的原因很多,不同原因要用不同的方法解除sleep()和wait()是兩個常用的引起阻塞的方法中斷線程在程序中常常調(diào)用interrupt()來終止線程interrupt()不僅可中斷正在運(yùn)行的線程,而且也能中斷處于blocked狀態(tài)的線程interrupt()會拋出InterruptedException異常測試線程是否被中斷的方法voidinterrupt()向一個線程發(fā)送一個中斷請求,同時把這個線程的“interrupted”狀態(tài)置為true。若該線程處于“blocked”狀態(tài),會拋出InterruptedException異常staticbooleaninterrupted()檢測當(dāng)前線程是否已被中斷,并重置狀態(tài)“interrupted”值。即如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回falsebooleanisInterrupted()檢測當(dāng)前線程是否已被中斷,不改變狀態(tài)“interrupted”值第二節(jié)創(chuàng)建線程創(chuàng)建線程有兩種方法繼承Thread類創(chuàng)建線程實(shí)現(xiàn)Runnable接口創(chuàng)建線程繼承Thread類java.lang.Thread是Java中用來表示線程的類如果將一個類定義為Thread的子類,那么這個類的對象就可以用來表示線程類Thread中典型的構(gòu)造方法Thread(ThreadGroupgroup,Runnabletarget,Stringname)name作為新線程的名稱,且是線程組group中的一員target必須實(shí)現(xiàn)接口Runnable,它是另一個線程對象創(chuàng)建線程的過程從Thread類派生出一個子類,在類中實(shí)現(xiàn)run()classLefthandextendsThread{ publicvoidrun(){……} //線程體}然后用該類創(chuàng)建一個對象Lefthandleft=newLefthand();用start()方法啟動線程left.start();程序10.1實(shí)現(xiàn)Runnable接口創(chuàng)建線程Runnable是Java中用以實(shí)現(xiàn)線程的接口,從任何實(shí)現(xiàn)線程功能的類都必須實(shí)現(xiàn)該接口Thread類實(shí)際上就是因?yàn)閷?shí)現(xiàn)了Runnable接口,所以它的子類才相應(yīng)具有線程功能Runnable接口中只定義了一個方法就是run()方法,也就是線程體用Runnable()接口實(shí)現(xiàn)多線程時,也必須實(shí)現(xiàn)run()方法,也需要使用start()啟動線程編寫線程體publicclassxyzimplementsRunnable{ inti; publicvoidrun(){ while(true){ System.out.println("Hello"+i++); } }}利用類xyz可以構(gòu)造一個線程Runnabler=newxyz();Threadt=newThread(r);程序10.2創(chuàng)建線程兩種方法的適用條件適用于采用實(shí)現(xiàn)Runnable接口方法的情況因?yàn)镴ava只允許單繼承,如果一個類已經(jīng)繼承了Thread,就不能再繼承其他類,在一些情況下,這就被迫采用實(shí)現(xiàn)Runnable的方法另外,由于原來的線程采用的是實(shí)現(xiàn)Runnable接口的方法,可能會出于保持程序風(fēng)格的一貫性而繼續(xù)使用這種方法創(chuàng)建線程兩種方法的適用條件適用于采用繼承Thread方法的情況當(dāng)一個run()方法置于Thread類的子類中時,this實(shí)際上引用的是控制當(dāng)前運(yùn)行系統(tǒng)的Thread實(shí)例,所以,代碼不必寫得像下面這樣繁瑣:Thread.currentThread().getState();而可簡單地寫為:getState();可以直接調(diào)用Thread類中的方法,代碼直觀,所以許多Java程序員愿意使用繼承Thread的方法第三節(jié)線程的基本控制線程的啟動要使線程真正在Java環(huán)境中運(yùn)行,必須通過方法start()來啟動start()方法也在Thread類中線程的操作方法start()啟動線程對象,讓線程從新建狀態(tài)轉(zhuǎn)為就緒狀態(tài)run()用來定義線程對象被調(diào)度之后所執(zhí)行的操作,用戶必須重寫run()方法yield()強(qiáng)制終止線程的執(zhí)行isAlive()測試當(dāng)前線程是否在活動sleep(intmillsecond)使線程休眠一段時間,時間長短由millsecond決定,單位為毫秒voidwait()使線程處于等待狀態(tài)線程的調(diào)度雖然就緒線程已經(jīng)可以運(yùn)行,但這并不意味著這個線程一定能夠立刻運(yùn)行CPU在同一時間只能分配給一個線程做一件事那么當(dāng)有多于一個的線程工作時,CPU是如何分配的?這就是線程的調(diào)度問題在Java中,線程調(diào)度通常是搶占式,而不是時間片式搶占式調(diào)度是指可能有多個線程準(zhǔn)備運(yùn)行,但只有一個在真正運(yùn)行一個線程獲得執(zhí)行權(quán),這個線程將持續(xù)運(yùn)行下去,直到它運(yùn)行結(jié)束或因?yàn)槟撤N原因而阻塞,再或者有另一個高優(yōu)先級線程就緒,最后一種情況稱為低優(yōu)先級線程被高優(yōu)先級線程所搶占線程調(diào)度采用的優(yōu)先級策略優(yōu)先級高的先執(zhí)行,優(yōu)先級低的后執(zhí)行每個線程創(chuàng)建時都會被自動分配一個優(yōu)先級,默認(rèn)時,繼承其父類的優(yōu)先級任務(wù)緊急的線程,其優(yōu)先級較高同優(yōu)先級的線程按“先進(jìn)先出”的調(diào)度原則與線程優(yōu)先級有關(guān)的靜態(tài)量MAX_PRIORITY:最高優(yōu)先級,值為10MIN_PRIORITY:最低優(yōu)先級,值為1NORM_PRIORITY:默認(rèn)優(yōu)先級,值為5有關(guān)優(yōu)先級的常用方法voidsetPriority(intnewPriority)重置線程優(yōu)先級intgetPriority()獲得當(dāng)前線程的優(yōu)先級staticvoidyield()暫停當(dāng)前正在執(zhí)行的線程,即讓當(dāng)前線程放棄執(zhí)行權(quán)線程被阻塞的原因可能是因?yàn)閳?zhí)行了Thread.sleep()調(diào)用,故意讓它暫停一段時間也可能是因?yàn)樾枰却粋€較慢的外部設(shè)備例如磁盤或用戶操作的鍵盤線程隊(duì)列所有被阻塞的線程按次序排列,組成一個阻塞隊(duì)列所有就緒但沒有運(yùn)行的線程則根據(jù)其優(yōu)先級排入一個就緒隊(duì)列當(dāng)CPU空閑時,如果就緒隊(duì)列不空,隊(duì)列中第一個具有最高優(yōu)先級的線程將運(yùn)行當(dāng)一個線程被搶占而停止運(yùn)行時,它的運(yùn)行態(tài)被改變并放到就緒隊(duì)列的隊(duì)尾同樣,一個被阻塞的線程就緒后通常也放到就緒隊(duì)列的隊(duì)尾結(jié)束線程當(dāng)一個線程從run()方法的結(jié)尾處返回時,它自動消亡并且不能再被運(yùn)行,可以將其理解為自然死亡另一種情況是遇到異常使得線程結(jié)束,可以將其理解為強(qiáng)迫死亡還可以使用interrupt()方法中斷線程的執(zhí)行查詢線程狀態(tài)在程序代碼中,可以利用Thread類中的靜態(tài)方法currentThread()來引用正在運(yùn)行的線程有時候可能不知道一個線程的運(yùn)行狀態(tài),這時可以使用方法isAlive()來獲取一個線程是否還在活動狀態(tài)的信息活動狀態(tài)不意味著這個線程正在執(zhí)行,而只說明這個線程已被啟動掛起線程暫停一個線程也稱為掛起在掛起之后,必須重新喚醒線程進(jìn)入運(yùn)行掛起線程的方法——sleep()方法sleep()用于暫時停止一個線程的執(zhí)行掛起喚醒線程不是休眠期滿后就立刻被喚醒,因?yàn)榇藭r其他線程可能正在執(zhí)行重新調(diào)度只在以下幾種情況下才會發(fā)生被喚醒的線程具有更高的優(yōu)先級正在執(zhí)行的線程因?yàn)槠渌虮蛔枞绦蛱幱谥С謺r間片的系統(tǒng)中掛起線程的方法wait()和notify()/notifyAll()方法wait()方法導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的notify()方法或notifyAll()方法,才能喚醒線程join()方法join()將引起現(xiàn)行線程等待,直至方法join()所調(diào)用的線程結(jié)束比如在線程B中調(diào)用了線程A的join()方法,直到線程A執(zhí)行完畢后,才會繼續(xù)執(zhí)行線程B可以想像成將線程A加入到當(dāng)前線程B中第四節(jié)線程的互斥一些同時運(yùn)行的線程需要共享數(shù)據(jù)此時,每個線程就必須要考慮與它一起共享數(shù)據(jù)的其他線程的狀態(tài)與行為,否則的話就不能保證共享數(shù)據(jù)的一致性,因而也就不能保證程序的正確性棧的示例棧具有“后進(jìn)先出”模式,它使用下標(biāo)值idx表示棧中下一個放置元素的位置有兩個獨(dú)立的線程A和B都具有對這個類的同一個對象的引用,線程A負(fù)責(zé)入棧,線程B負(fù)責(zé)出棧要求線程A放入棧中的數(shù)據(jù)都要由線程B讀出,不重不漏假設(shè)此時棧中已經(jīng)有字符1和2,當(dāng)前線程A要入棧一個字符3,調(diào)用push(3),執(zhí)行了語句data[idx]=c;后被其他線程搶占了,此時尚未執(zhí)行idx++語句。故idx指向最后入棧的字符的下標(biāo)data123idx=2^如果此時線程A馬上被喚醒,可以繼續(xù)修正idx的值,從而完成一次完整的入棧操作如若不然,入棧操作執(zhí)行了一半若恰巧線程B此時正占有CPU,調(diào)用pop(),執(zhí)行出棧操作,則它返回的字符是2,因?yàn)樗葓?zhí)行idx--語句,idx的值變?yōu)?,返回的是data[1]處的字符,即字符2字符3被漏掉了多線程訪問共享數(shù)據(jù)時通常會引起問題產(chǎn)生這種問題的原因是對共享資源訪問的不完整性需要尋找一種機(jī)制來保證對共享數(shù)據(jù)操作的完整性這種完整性稱為共享數(shù)據(jù)操作的同步共享數(shù)據(jù)叫做條件變量鎖定標(biāo)志可以禁止線程在完成代碼關(guān)鍵部分時被切換這個關(guān)鍵代碼部分,對于線程A就是入棧操作及下標(biāo)值增加這兩個動作,對于線程B就是下標(biāo)值遞減及出棧操作這兩個動作它們要么一起完成,要么都不執(zhí)行在Java中,提供了一個特殊的鎖定標(biāo)志來處理共享數(shù)據(jù)的訪問對象的鎖定標(biāo)志引入“對象互斥鎖”的概念,也稱為監(jiān)視器使用它來實(shí)現(xiàn)不同線程對共享數(shù)據(jù)操作的同步“對象互斥鎖”阻止多個線程同時訪問同一個條件變量Java可以為每一個對象的實(shí)例配有一個“對象互斥鎖”實(shí)現(xiàn)“對象互斥鎖”的方法用關(guān)鍵字synchronized來聲明一個方法或一段代碼塊,該方法或代碼塊在執(zhí)行時會獲取對象實(shí)例的內(nèi)置鎖,其他線程必須等待鎖的釋放才能訪問該方法或代碼塊中的共享數(shù)據(jù)使用關(guān)鍵字volatile來聲明一個共享數(shù)據(jù)(變量)。但是,使用volatile關(guān)鍵字只能保證數(shù)據(jù)的可見性和有序性,無法實(shí)現(xiàn)對共享數(shù)據(jù)的原子操作,因此不能完全替代使用synchronized實(shí)現(xiàn)同步鎖定標(biāo)志示例classstack{ intidx=0; chardata[]=newchar[6]; publicvoidpush(charc){ synchronized(this){//增加同步標(biāo)志 data[idx]=c; idx++; } }}鎖定標(biāo)志方法publiccharpop(){ synchronized(this){ //增加同步標(biāo)志 idx--; returndata[idx]; }}synchronized(this)現(xiàn)在pop()和push()操作的部分增加了一個對synchronized(this)的調(diào)用,在第一個線程擁有鎖定標(biāo)記時,如果另一個線程企圖執(zhí)行synchronized(this)中的語句時,它將從對象this中索取鎖定標(biāo)記因?yàn)檫@個標(biāo)記不可得,故該線程不能繼續(xù)執(zhí)行實(shí)際上這個線程將加入一個等待隊(duì)列,該等待隊(duì)列與對象鎖定標(biāo)志相連,當(dāng)標(biāo)志被返還給對象時,等待標(biāo)志的第一個線程將得到該標(biāo)志并繼續(xù)運(yùn)行因?yàn)榈却粋€對象的鎖定標(biāo)志的線程要等到持有該標(biāo)志的線程將其返還后才能繼續(xù)運(yùn)行,所以在不使用該標(biāo)志時將其返還就顯得十分重要了事實(shí)上,當(dāng)持有鎖定標(biāo)志的線程運(yùn)行完synchronized()調(diào)用包含的程序塊后,這個標(biāo)志將會被自動返還如果一個線程兩次調(diào)用了同一個對象,在退出最外層后這個標(biāo)志也將被正確釋放,而在退出內(nèi)層時則不會執(zhí)行釋放用synchronized標(biāo)識的代碼段或方法即為“對象互斥鎖”鎖住的部分如果一個程序內(nèi)有兩個或以上的方法使用synchronized標(biāo)志,則它們在同一個“對象互斥鎖”管理之下synchronized()語句publicvoidpush(charc){ synchronized(this){

┊ }}或是publicsynchronizedvoidpush(charc){

┊}第五節(jié)線程的同步為了完成多個任務(wù),常創(chuàng)建多個線程,它們可能毫不相關(guān),但有時它們完成的任務(wù)在某種程度上有一定的關(guān)系,此時就需要線程之間有一些交互使用方法wait()和notify()/notifyall()實(shí)現(xiàn)線程的交互操作系統(tǒng)中的生產(chǎn)者消費(fèi)者問題,就是一個經(jīng)典的同步問題若共享對象中只能存放一個數(shù)據(jù),可能出現(xiàn)以下問題:生產(chǎn)者比消費(fèi)者快時,消費(fèi)者會漏掉一些數(shù)據(jù)沒有取到消費(fèi)者比生產(chǎn)者快時,消費(fèi)者取相同的數(shù)據(jù)解決方法Java中的每個對象實(shí)例都有兩個線程隊(duì)列和它相連第一個用來排列等待鎖定標(biāo)志的線程第二個則用來實(shí)現(xiàn)wait()和notify()的交互機(jī)制類java.lang.Object中定義了三個方法wait()notify()notifyAll()wait()方法導(dǎo)致當(dāng)前的線程等待,它的作用是讓當(dāng)前線程釋放其所持有的“對象互斥鎖”,進(jìn)入wait隊(duì)列(等待隊(duì)列)notify()/notifyAll()方法的作用是喚醒一個或所有正在等待隊(duì)列中等待的線程,并將它(們)移入等待同一個“對象互斥鎖”的隊(duì)列notify()/notifyAll()方法和wait()方法都只能在被聲明為synchronized的方法或代碼段中調(diào)用方法

溫馨提示

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

最新文檔

評論

0/150

提交評論