《面向?qū)ο蟪绦蛟O計項目教程》課件 項目12 認識多線程_第1頁
《面向?qū)ο蟪绦蛟O計項目教程》課件 項目12 認識多線程_第2頁
《面向?qū)ο蟪绦蛟O計項目教程》課件 項目12 認識多線程_第3頁
《面向?qū)ο蟪绦蛟O計項目教程》課件 項目12 認識多線程_第4頁
《面向?qū)ο蟪绦蛟O計項目教程》課件 項目12 認識多線程_第5頁
已閱讀5頁,還剩36頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

面向?qū)ο蟪绦蛟O計項目教程本章學習目標:●

掌握線程創(chuàng)建的過程●

掌握線程的生命周期●掌了解線程同步機制以及線程通信●

了解線程的優(yōu)先級●

掌握線程的同步與死鎖項目12認識多線程任務1part了解線程在Windows操作系統(tǒng)中,右擊任務欄,選擇“啟動任務管理器”菜單命令,可以打開“Windows任務管理器”窗口,該窗口中的“進程”選項卡中顯示系統(tǒng)當前正在運行的進程,如圖12.1所示。1.1線程和進程進程

進程具有如下三個特征:

(1)獨立性:進程是操作系統(tǒng)中獨立存在的實體,擁有自己獨立的資源,每個進行都擁有自己私有的地址空間,其他進程不可以直接訪問該地址空間,除非進程本身允許的情況下才能進行訪問。

(2)動態(tài)性:程序只是一個靜態(tài)的指令集合,只有當程序進入內(nèi)存運行時,才變成一個進程。進程是一個正在內(nèi)存中運行的、動態(tài)的指令集合,進程具有自己的生命周期和各種不同狀態(tài)。

(3)并發(fā)性:多個進程可以在單個處理器上并發(fā)執(zhí)行,多個進程之間互不影響。1.1線程和進程

線程是進程的組成部分,一個線程必須在一個進程之內(nèi),而一個進程可以擁有多個線程,一個進程中至少有一個線程。線程是最小的處理單位,線程可以擁有自己的堆棧、計數(shù)器和局部變量,當不能擁有系統(tǒng)資源,多個線程共享其所在進程的系統(tǒng)資源。線程可以完成一定的任務,使用多線程可以在一個程序中同時完成多個任務,在更低的層次中引入多任務處理。

多線程在多CPU的計算機中可以實現(xiàn)真正物理上的同時執(zhí)行;而對于單CPU的計算機實現(xiàn)的只是邏輯上的同時執(zhí)行,在每個時刻,真正執(zhí)行的只有一個線程,由操作系統(tǒng)進行線程管理調(diào)度,但由于CPU的速度很快,讓人感到像是多個線程在同時執(zhí)行。1.1線程和進程線程

多線程擴展了多進程的概念,使得同一個進程可以同時并發(fā)處理多個任務。因此,線程也被稱作輕量級進程。多進程與多線程是多任務的兩種類型,兩者之間的主要區(qū)別如下:

(1)進程之間的數(shù)據(jù)塊是相互獨立的,彼此互不影響,進程之間需要通過信號、管道等進行交互。

(2)多線程之間的數(shù)據(jù)塊可以共享,一個進程中的多個線程可以共享程序段、數(shù)據(jù)段等資源。多線程比多進程更便于資源共享,同時Java提供的同步機制還可以解決線程之間的數(shù)據(jù)完整性問題,使得多線程設計更易發(fā)揮作用。多線程編程的優(yōu)點如下:

(1)多線程之間共享內(nèi)存,節(jié)約系統(tǒng)資源成本;

(2)充分利用CPU,執(zhí)行并發(fā)任務效率高;

(3)Java內(nèi)置多線程功能支持,簡化編程模型

(4)GUI應用通過啟動單獨線程收集用戶界面事件,簡化異步事件處理,使GUI界面的交互性更好。1.1線程和進程Java線程模型提供線程所必需的功能支持,基本的Java線程模型有Thread類、Runnable接口、Callable接口和Future接口等,這些線程模型都是面向?qū)ο蟮?。Thread類將線程所必需的功能進行封裝,其常用的方法如表12-1所示。1.2Java線程模型Thread類的run()方法是線程中最重要的方法,該方法用于執(zhí)行線程要完成的任務;當創(chuàng)建一個線程時,要完成自己的任務,則需要重寫run()方法。此外,Thread類還提供了start()方法,該方法用于負責線程的啟動;當調(diào)用start()方法成功地啟動線程后,系統(tǒng)會自動調(diào)用Thread類的run()方法來執(zhí)行線程。因此,任何繼承Thread類的線程都可以通過start()方法來啟動。Runnable接口用于標識某個Java類可否作為線程類,該接口只有一個抽象方法run(),即線程中最重要的執(zhí)行體,用于執(zhí)行線程中所要完成的任務。Runnable接口定義在java.lang包中,定義代碼如下所示。packagejava.lang;publicinterfaceRunnable{ publicabstractvoidrun();}1.2Java線程模型Callable接口是Java5新增的接口,該接口中提供一個call()方法作為線程的執(zhí)行體。call()方法比run()方法功能更強大,call()方法可以有返回值,也可以聲明拋出異常。Callable接口定義在java.util.concurrent包中,定義代碼如下所示。packagejava.util.concurrent;publicinterfaceCallable<V>{ Vcall()throwsException;}1.2Java線程模型Future接口用來接收Callable接口中call()方法的返回值。Future接口提供一些方法用于控制與其關(guān)聯(lián)的Callable任務。Future接口提供的方法如表12-2所示。1.2Java線程模型Callable接口有泛型限制,該接口中的泛型形參類型與call()方法返回值的類型相同;而且Callable接口是函數(shù)式接口,因此從Java8開始可以使用Lambda表達式創(chuàng)建Callable對象。

每個能夠獨立運行的程序就是一個進程,每個進程至少包含一個線程,即主線程。在Java語言中,每個能夠獨立運行的Java程序都至少有一個主線程,且在程序啟動時,JVM會自動創(chuàng)建一個主線程來執(zhí)行該程序中的main()方法。因此,主線程有以下兩個特點:

(1)一個進程肯定包含一個主線程

(2)主線程用來執(zhí)行main()方法

1.3主線程主線程任務2part創(chuàng)建線程

基于Java線程模型,創(chuàng)建線程的方式有三種:

(1)第一種方式是繼承Thread類,重寫Thread類中的run()方法,直接創(chuàng)建線程。

(2)第二種方式是實現(xiàn)Runnable接口,再通過Thread類和Runnable的實現(xiàn)類間接創(chuàng)建一個線程。

(3)第三種方式是使用Callable接口或Future接口間接創(chuàng)建線程。

創(chuàng)建線程2.1繼承Thread類

通過繼承Thread類來創(chuàng)建并啟動線程的步驟如下:

(1)定義一個子類繼承Thread類,并重寫run()方法。

(2)創(chuàng)建子類的實例,即實例化線程對象。

(3)調(diào)用線程對象的start()方法啟動該線程。Thread類的start()方法將調(diào)用run()方法,該方法用于啟動線程并運行。因此start()方法不能多次調(diào)用,當多次調(diào)用td.start()方法時會拋出一個IllegalThreadStateException異常。

繼承Thread類2.2實現(xiàn)Runable接口

創(chuàng)建線程的第二種方式是實現(xiàn)Runnable接口。Runnable接口中只有一個run()方法,一個類實現(xiàn)Runnable接口后,并不代表該類是個“線程”類,不能直接啟動線程,必須通過Thread類的實例來創(chuàng)建并啟動線程。通過Runnable接口創(chuàng)建并啟動線程的步驟如下:

(1)定義一個類實現(xiàn)Runnable接口,并實現(xiàn)該接口中的run()方法;

(2)創(chuàng)建一個Thread類的實例,將Runnable接口的實現(xiàn)類所創(chuàng)建的對象作為參數(shù)傳入Thread類的構(gòu)造方法中;

(3)調(diào)用Thread對象的start()方法啟動該線程。

2.3使用Callable和Future接口

創(chuàng)建線程的第三種方式是使用Callable和Future接口。Callable接口提供一個call()方法作為線程的執(zhí)行體,該方法的返回值使用Future接口來代表。從Java5開始,為Future接口提供一個FutureTask實現(xiàn)類,該類同時實現(xiàn)了Future和Runnable兩個接口,因此可以作為Thread類的target參數(shù)。使用Callable和Future接口的最大優(yōu)勢在于可以在線程執(zhí)行完成之后獲得執(zhí)行結(jié)果。

使用Callable和Future接口創(chuàng)建并啟動線程的步驟如下:

(1)創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該方法將作為線程的執(zhí)行體,并具有返回值;然后創(chuàng)建Callable實現(xiàn)類的實例。

(2)使用FutureTask類來包裝Callable對象,在FutureTask對象中封裝了Callable對象的call()方法的返回值。

(3)使用FutureTask對象作為Thread對象的target,創(chuàng)建并啟動新線程。

(4)調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。

任務3part掌握線程的生命周期

線程具有生命周期,當線程被創(chuàng)建并啟動后,不會立即進入執(zhí)行狀態(tài),也不會一直處于執(zhí)行狀態(tài)。在線程的生命周期中,要經(jīng)過5種狀態(tài):新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)。線程狀態(tài)之間的轉(zhuǎn)換如圖12-3所示。線程的生命周期本節(jié)介紹3.1新建和就緒狀態(tài)

當程序使用new關(guān)鍵字創(chuàng)建一個線程之后,該線程就處于新建狀態(tài)。新建狀態(tài)的線程沒有表現(xiàn)出任何動態(tài)特征,程序也不會執(zhí)行線程的執(zhí)行體。當線程對象調(diào)用start()方法之后,線程就處于就緒狀態(tài),相當于“等待執(zhí)行”。此時,調(diào)度程序就可以把CPU分配給該線程,JVM會為線程創(chuàng)建方法調(diào)用棧和程序計數(shù)器。處于就緒狀態(tài)的線程并沒有開始運行,只是表示該線程準備就緒等待執(zhí)行。

注意只能對新建狀態(tài)的線程調(diào)用start()方法,即new完一個線程后,只能調(diào)用一次start()方法,否則將引發(fā)IllegalThreadStateException異常。

3.2運行和阻塞狀態(tài)

處于就緒狀態(tài)的線程獲得CPU后,開始執(zhí)行run()方法的線程執(zhí)行體,此時該線程處于運行狀態(tài)。如果計算機的CPU是單核的,則在任何時刻只有一個線程處于運行狀態(tài)。一個線程開始運行后,不可能一直處于運行狀態(tài)。線程在運行過程中需要被中斷,目的是使其他線程獲得執(zhí)行的機會,線程調(diào)度的細節(jié)取決于底層平臺所采用的策略。

目前UNIX系統(tǒng)采用的是時間片算法策略,Windows系統(tǒng)采用的則是搶占式策略,另外一種小型設備(手機)則可能采用協(xié)作式調(diào)度策略。對于采用搶占式策略的系統(tǒng)而言,系統(tǒng)會給每個可執(zhí)行的線程一個小時間段來處理任務;當該時間段用完后,系統(tǒng)就會剝奪該線程所占用的資源,讓其他線程獲得執(zhí)行的機會。在選擇下一個線程時,系統(tǒng)會考慮線程的優(yōu)先級。運行和阻塞狀態(tài)當線程出現(xiàn)以下情況時,會進入阻塞狀態(tài):(1)調(diào)用sleep()方法,主動放棄所占用的處理器資源;(2)調(diào)用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞;(3)線程試圖獲得一個同步監(jiān)視器,但該同步監(jiān)視器正被其他線程所持有;(4)執(zhí)行條件還未滿足,調(diào)用wait()方法使線程進入等待狀態(tài),等待其他線程的通知;(5)程序調(diào)用了線程的suspend()方法將該線程掛起,但該方法容易導致死鎖,因此應該盡量避免使用。3.2運行和阻塞狀態(tài)

正在執(zhí)行的線程被阻塞之后,其他線程就可以獲得執(zhí)行的機會,被阻塞的線程會在合適的時機重新進入就緒狀態(tài),等待線程調(diào)度器再次調(diào)度。

當線程出現(xiàn)如下幾種情況時,線程可以解除阻塞進入就緒狀態(tài):

(1)調(diào)用sleep()方法的線程經(jīng)過了指定的時間;

(2)線程調(diào)用的阻塞式IO方法以經(jīng)返回;

(3)線程成功地獲得了同步監(jiān)視器;

(4)線程處于等待狀態(tài),其他線程調(diào)用notify()或notifyAll()方法發(fā)出了一個通知時,則線程回到就緒狀態(tài);

(5)處于掛起狀態(tài)的線程被調(diào)用了resume()恢復方法。3.2運行和阻塞狀態(tài)

在線程運行的過程中,可以通過sleep()方法使線程暫時停止運行,進入休眠狀態(tài)。在使用sleep()方法時需要注意以下兩點:

(1)sleep()方法的參數(shù)是以毫秒為基本單位,例如sleep(2000)則休眠2秒鐘;

(2)sleep()方法聲明了InterruptedException異常,因此調(diào)用sleep()方法時要么放在try…catch語句中捕獲該異常并處理,要么在方法后使用throws顯式聲明拋出該異常。

可以通過Thread類的isAlive()方法來判斷線程是否處于運行狀態(tài)。當線程處于就緒、運行和阻塞三種狀態(tài)時,isAlive()方法的返回值為true;當線程處于新建、死亡兩種狀態(tài)時,isAlive()方法的返回值為false。3.2死亡狀態(tài)線程結(jié)束后就處于死亡狀態(tài),結(jié)束線程有以下三種方式:(1)線程執(zhí)行完成run()或call()方法,線程正常結(jié)束;(2)線程拋出一個未捕獲的Exception或Error;(3)調(diào)用stop()方法直接停止線程,該方法容易導致死鎖,通常不推薦使用。3.3死亡狀態(tài)

主線程結(jié)束時,其他子線程不受任何影響,并不會隨主線程的結(jié)束而結(jié)束。一旦子線程啟動起來,子線程就擁有和主線程相同的地位,子線程不會受主線程的影響。

為了測試某個線程是否死亡,可以通過線程對象的isAlive()方法來獲得線程狀態(tài),當方法返回值為false時,線程處于死亡或新建狀態(tài)。不要試圖對一個已經(jīng)死亡的線程調(diào)用start()方法使其重新啟動,線程死亡就是死亡,該線程不可再次作為線程執(zhí)行。Thread類中的join()方法可以讓一個線程等待另一個線程完成后,繼續(xù)執(zhí)行原線程中的任務。當在某個程序執(zhí)行流中調(diào)用其他線程的join()方法時,當前線程將被阻塞,直到另一個線程執(zhí)行完為止。join()方法通常由使用線程的程序調(diào)用,當其他線程都執(zhí)行結(jié)束后,再調(diào)用主線程進一步操作。

3.3任務4part了解線程的優(yōu)先級Thread類提供三個靜態(tài)常量來標識線程的優(yōu)先級:

(1)MAX_PRIORITY:最高優(yōu)先級,其值為10;

(2)NORM_PRIORITY:普通優(yōu)先級,其值為5;

(3)MIN_PRIORITY:最低優(yōu)先級,其值為1。線程的優(yōu)先級Thread類提供了setPriority()方法來對線程的優(yōu)先級進行設置,而getPriority()方法來獲取線程的優(yōu)先級。setPriority()方法的參數(shù)是一個整數(shù)(1~10),也可以使用Thread類提供的三個優(yōu)先級靜態(tài)常量。

線程的優(yōu)先級高度依賴于操作系統(tǒng),并不是所有的操作系統(tǒng)都支持Java的10個優(yōu)先級,例如Windows2000僅提供7個優(yōu)先級。因此,盡量避免直接使用整數(shù)給線程指定優(yōu)先級,提倡使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三個優(yōu)先級靜態(tài)常量。線程的優(yōu)先級任務5part掌握線程的同步5.1同步代碼塊

使用同步代碼塊實現(xiàn)同步功能,只需將對實例的訪問語句放入一個同步塊中,其語法格式如下:synchronized(object){ //需要同步的代碼塊}

其中:synchronized是同步關(guān)鍵字;object是同步監(jiān)視器,其數(shù)據(jù)類型不能是基本數(shù)據(jù)類型。線程開始執(zhí)行同步代碼之前,必須先獲得同步監(jiān)視器的鎖定,并且,任何時刻只能有一個線程可以獲得對同步監(jiān)視器的鎖定,當同步代碼塊執(zhí)行完成后,該線程會釋放對該同步監(jiān)視器的鎖定。5.2同步方法

同步方法是使用synchronized關(guān)鍵字修飾的方法,其聲明的語法格式如下:[訪問修飾符]synchronized返回類型方法名([參數(shù)列表]){ //方法體}

其中:synchronized關(guān)鍵字修飾的實例方法無須顯式地指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,即該方法所屬的對象。一旦一個線程進入一個實例的任何同步方法,其他線程將不能進入該實例的所有同步方法,但該實例的非同步方法仍然能夠被調(diào)用。

5.3同步鎖

同步鎖Lock是一種更強大的線程同步機制,通過顯式定義同步鎖對象來實現(xiàn)線程同步。同步鎖提供了比同步代碼塊、同步方法更廣泛的鎖定操作,實現(xiàn)更靈活。Lock是控制多個線程對共享資源進行訪問的工具,能夠?qū)蚕碣Y源進行獨占訪問。每次只能有一個線程對Lock對象加鎖,線程訪問共享資源之前需要先獲得Lock對象。某些鎖可能允許對共享資源并發(fā)訪問,如ReadWriteLock(讀寫鎖)。Lock和ReadWriteLock是Java5提供的關(guān)于鎖的兩個根接口,并為Lock提供了ReentrantLock(可重入鎖)實現(xiàn)類,為ReadWriteLock提供了ReentrantReadWriteLock實現(xiàn)類。從Java8開始,又新增了StampedeLock類,可以替代傳統(tǒng)的ReentrantReadWriteLock類。同步鎖ReentrantLock類是常用的可重入同步鎖,該類對象可以顯式地加鎖、釋放鎖。使用ReentrantLock類的步驟如下:

(1)定義一個ReentrantLock鎖對象,該對象是final常量;privatefinalReentrantLocklock=newReentrantLock();

(2)在需要保證線程安全的代碼之前增加“加鎖”操作;lock.lock();

(3)在執(zhí)行完線程安全的代碼后“釋放鎖”。lock.unlock();5.3同步鎖下述代碼示例了使用ReentrantLock鎖的基本步驟://1.定義鎖對象privatefinalReentrantLocklock=newReentrantLock();...//定義需要保證線程安全的方法publicvoidmyMethod(){//2.加鎖lock.lock();try{//需要保證線程安全的代碼...}finally{//3.釋放鎖lock.unlock();}}

其中:加鎖和釋放鎖都需要放在線程安全的方法中;lock.unlock()放在finally語句中,不管發(fā)生異常與否,都需要釋放鎖。5.3任務6part實現(xiàn)線程通信線程通信可以使用Object類中定義的wait()、notify()和notifyAll()方法,使線程之間相互進行事件通知。在執(zhí)行這些方法時,必須同時擁有相關(guān)對象的鎖。

(1)wait()方法:讓當前線程等待,并釋放對象鎖,直到其他線程調(diào)用該監(jiān)視器的notify()或notifyAll()方法來喚醒該線程。wait()方法也可以帶一個參數(shù),用于指明等待的時間,使用這種方式不需要notify()或notifyAll()方法來喚醒。wait()方法只能在同步方法中調(diào)用。

溫馨提示

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

評論

0/150

提交評論