




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
《
Java程序設計》課程1、進程和線程的概念主要內容2、多線程的優(yōu)缺點3、線程的創(chuàng)建方式Java程序設計4、線程的狀態(tài)和常用方法《
Java程序設計》課程5、線程的安全問題主要內容6、線程的同步與死鎖7、生產者和消費者模型Java程序設計8、Object類對線程的支持《Java程序設計》課程學習目標能了解進程和線程的概念;能理解多線程的優(yōu)點和缺點;能正確掌握線程的定義的兩種形式;能正確掌握線程的狀態(tài)和常用方法;能正確理解線程中鎖的概念;能正確解決線程同步問題;能理解線程死鎖產生的原因;能理解并掌握生產者和消費者模型;《Java程序設計》課程
10.1進程與線程10.1.1知識準備(1)進程和線程的概念線程的概念來源于計算機的操作系統(tǒng)的進程的概念。進程是一個程序關于某個數據集的一次運行。也就是說,進程是運行中的程序,是程序的一次運行活動。大家可以打開操作系統(tǒng)的“任務管理器”中的進程選項查看系統(tǒng)正在運行的進程。線程就是進程中一個負責程序執(zhí)行的控制單元(執(zhí)行路徑),一個進程中可以多執(zhí)行路徑,稱之為多線程。一個進程中至少要有一個線程。每一個線程都有自己運行的內容。這個內容可以稱為線程要執(zhí)行的任務。開啟多個線程的目的是為了同時運行多部分代碼。所有的程序員都熟悉順序程序的編寫,如我們編寫求三角形的三條邊長之和的程序就是順序程序。順序程序都有開始、執(zhí)行序列和結束,在程序執(zhí)行的任何時刻,只有一個執(zhí)行點。線程(thread)則是進程中的一個單個的順序控制流。單線程的概念很簡單,如圖10.1所示?!禞ava程序設計》課程
10.1進程與線程多線程(multi-thread)是指在單個的程序內可以同時運行多個不同的線程完成不同的任務,圖10.2說明了一個程序中同時有兩個線程運行。一個線程兩個線程圖10.1單線程程序示意圖
圖10.2多線程程序示意圖有些程序中需要多個控制流并行執(zhí)行。例如以下程序段:..\超鏈接文件\圖10.1.1.doc上面的代碼段中,在單線程中,前一個循環(huán)如果不執(zhí)行完,那么就不可能執(zhí)行第二個循環(huán)。要使兩個循環(huán)同時執(zhí)行,需要編寫多線程的程序。《Java程序設計》課程
10.1進程與線程(2)多線程的優(yōu)缺點如果一能一次只做一件事,并把它做好,那當然不錯。但萬事萬物并非如此,人體一直在并發(fā)地做許多事情。如:人可以并發(fā)地進行呼吸、血液循環(huán)、消化食物等活動,所有感覺器官:視覺、觸覺、嗅覺均能并發(fā)工作。計算機也能并發(fā)工作,個人電腦可以一邊編程,一邊打印,這已經是很平常的事。很多應用程序是用多線程實現的,如360安全衛(wèi)士,我們打開360安全衛(wèi)士后,可以“掃描系統(tǒng)漏洞”,同時也可以“清理系統(tǒng)垃圾文件”,也可以同時掃描病毒和殺毒。這就是典型的多線程應用程序,即看上去在同一時刻,可以運行多個程序,即可以同時做多個事情。注意:多線程的優(yōu)勢主要有:①減輕編寫交互頻繁、涉及面多的程序的困難;②程序的吞吐量會得到改善;③有多個處理器的系統(tǒng),可以并發(fā)運行不同的線程.(否則,任何時刻只有一個線程在運行)。換句話說,多線程最大的優(yōu)勢是:解決了多部分代碼同時運行的問題。但是應用程序的執(zhí)行都是CPU在做著快速的切換完成的。這個切換是隨機的,是由時間片來切換的。即在同一個時間點,只能執(zhí)行一個程序。所以當系統(tǒng)同時開啟多個應用程序后,每個應用程序被切換的概率就下降了,所以效率就下降了?!禞ava程序設計》課程10.2線程的實現10.2.1項目(10-1)描述模擬多線程賣票程序(車票作為共享資源的方式)。10.2.2項目知識準備在JAVA中如果要實現多線程,由兩種方法可以實現:一種是繼承Thread類,另一種就是實現Runnable接口。下面分別介紹這兩種方式的使用?!禞ava程序設計》課程10.2線程的實現(1)繼承Thread類Thread類是java.lang包中定義的,一個類只要繼承了Thread類,則此類就成了多線程操作類。在Thread類的子類中,必須明確地重寫Thread類中的run()方法,此方法為線程的主體,即線程的任務。通過繼承Thread類,實現多線程的語法格式如下所示:..\超鏈接文件\圖10.1.1.doc案例10-1:繼承Thread類實現多線程(一)。(1)先定義Thread線程的子類MyThread..\超鏈接文件\圖10.1.1.doc在MyThread類中定義了私有的成員變量name,和一個參數的構造方法publicMyThread(Stringname),并重寫了父類的run()方法;《Java程序設計》課程10.2線程的實現(2)再定義一個功能測試類ThreadTest1,在本類的main()方法中定義2個MyThread類的對象,并調用對象的run()方法,代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果如圖10.3所示:圖10.3案例10-1的運行結果《Java程序設計》課程10.2線程的實現多次執(zhí)行程序后,通過觀察程序的運行結果,發(fā)現程序始終是先執(zhí)行完mt1對象之后再執(zhí)行mt2對象,并沒有出現我們預期的那樣交錯運行,也就是說,此時線程實際上并沒有啟動,還是屬于順序的執(zhí)行方式,那么如何啟動一個線程呢?注意:如果要正確的啟動線程,是不能直接調用run()方法的,而應該是調用從Thread類中繼承而來的start()方法。是的,要想啟動一個線程,則需要執(zhí)行線程對象的start()方法。請看案例10-2案例10-2:繼承Thread類實現多線程(二)。(1)先定義Thread線程的子類MyThread..\超鏈接文件\圖10.1.1.doc在MyThread類中定義了私有的成員變量name,和一個參數的構造方法publicMyThread(Stringname),并重寫了父類的run()方法;《Java程序設計》課程10.2線程的實現(2)再定義一個功能測試類ThreadTest2,在本類的main()方法中定義2個MyThread類的對象,并調用對象的run()方法,代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果(可能的結果之一)如圖10.4所示:從程序的運行結果中可以發(fā)現,兩個線程現在是交錯運行的,哪個線程對象搶到了CPU資源,哪個線程就可以運行,所以程序每次的運行結果是不一樣的,在線程啟動時雖然調用的是start()方法,但實際上調用的卻是run()方法。即執(zhí)行的是線程的任務。重點:調用從Thread類中繼承而來的start()方法可以啟動一個線程,并自動運行線程中的run()方法。思考:為什么啟動多線程時需要調用start()方法,而不是直接調用run()方法呢。答:線程的運行需要本機操作系統(tǒng)支持。首先來看一下start()方法在Thread類中的定義?!禞ava程序設計》課程10.2線程的實現通過分析代碼可以得出以下注意點:注意:從以上代碼中可以發(fā)現,在一個類中的start()方法調用時可能會拋出“IllegalThreadStateException”異常,一般在重復調用start()方法時會拋出這個異常。而且實際上此處真正調用的是start0()方法,此方法在聲明處使用了native關鍵字聲明,此關鍵字表示調用本機的操作系統(tǒng)函數,因為多線程的實現需要依靠底層操作系統(tǒng)支持。如果一個類通過繼承Thread類來實現多線程,那么只能調用一次start()方法,如果多次調用,則會拋出“IllegalThreadStateException”異常。案例10-3:重復調用start()方法的案例。(1)先定義Thread線程的子類MyThread
..\超鏈接文件\圖10.1.1.doc在MyThread類中定義了私有的成員變量name,和一個參數的構造方法publicMyThread(Stringname),并重寫了父類的run()方法;(2)再定義一個功能測試類ThreadTest3,在本類的main()方法中定義2個MyThread類的對象,并調用對象的run()方法,代碼如下:..\超鏈接文件\圖10.1.1.doc《Java程序設計》課程10.2線程的實現(3)測試運行,顯示結果如圖10.5所示:將會拋出IllegalThreadStateException異常。..\超鏈接文件\圖10.1.1.doc以上程序錯誤本身并不難理解,只要參照Thread類的定義即可理解。當然,如果一個類只能繼承Thread類才能實現多線程,則必定會受到單繼承局限的影響。所以一般情況下,要想實現多線程還可以通過實現Runnable接口來完成。(2)實現Runnable接口在Java中也可以通過實現Runnable接口的方式實現多線程,Runnable接口中只定義了一個抽象方法:publicvoidrun();使用Runnable接口實現多線程的格式如下:..\超鏈接文件\圖10.1.1.doc下面就使用此格式進行多線程的實現。繼承Runnable接口實現多線,代碼如下:
..\超鏈接文件\圖10.1.1.doc《Java程序設計》課程10.2線程的實現以上代碼通過實現Runnable接口實現多線程,但是這樣一來就會有新的問題產生,從之前的代碼可以清楚地知道,要想啟動一個多線程必須要使用start()方法來完成,如果繼承了Thread類,則可以直接從Thread類中使用start()方法,但是現在實現的是Runnable接口,那么該如何啟動多線程呢?實際上,此時還是要依靠Thread類完成啟動,在Thread類中提供了publicThread(Runnabletarget)和publicThread(Runnabletarget,Stringname)兩個構造方法。這兩個構造方法都可以接收Runnable的子類實例對象,所以就可以通過此知識點來啟動多線程,具體代碼請看案例10-4。案例10-4:使用Runnable接口和Thread類配合,開啟多線程。(1)先定義實現Runnable接口的子類MyThread
..\超鏈接文件\圖10.1.1.doc在MyThread類中定義了私有的成員變量name,和一個參數的構造方法publicMyThread(Stringname),并重寫了Runnable接口中的run()方法;《Java程序設計》課程10.2線程的實現(2)再定義一個功能測試類RunnableTest1,在本類的main()方法中定義2個MyThread類的對象,并定義了2個Thread類的實例,并調用該實例對象的run()方法,代碼如下:
..\超鏈接文件\圖10.1.1.doc注意:Threadt1=newThread(mt1);此代碼是將Thread對象t1與Runnable接口實現類的對象mt1相關聯的關鍵語句。這就意味著:t1.start();語句執(zhí)行后,t1線程就運行起來了,t1就自動調用mt1對象中run()方法,而不是執(zhí)行t1中默認的(即來自Thread類本身的)run()方法。(3)測試運行,顯示結果(可能結果之一)如圖10.6所示。圖10.6案例10-4的運行結果《Java程序設計》課程10.2線程的實現重點:從以上兩種實現可以發(fā)現,無論使用哪種方式,最終都必須依靠Thread類才能啟動多線程。(3)Thread類和Runnable接口的比較通過Thread類和Runnable接口都可以實現多線程,那么兩者有哪些聯系和區(qū)別呢?下面觀察Thread類的定義。..\超鏈接文件\圖10.1.1.doc從Thread類的定義可以清楚地發(fā)現,Thread類也是Runnable接口的子類,但是在Thread類中并沒有完全地實現Runnable接口中的run()方法,下面是Thread類的部分定義。從上面的定義中可以發(fā)現,在Thread類中的run()方法調用的是Runnable接口中的run()方法,也就是說此方法是由Runnable子類完成的,所以如果通過繼承Thread類實現多線程,則必須重寫run()方法。實際上Thread類和Runnable接口之間在使用上也是有區(qū)別的,如果一個類繼承Thread類,則不適合用于多個線程共享資源的情況,而實現了Runnable接口,就可以方便地實現資源的共享。案例10-5:賣票程序,用來演示通過繼承Thread類,多線程之間不能共享資源。(1)先定義繼承Thread父類的子類MyThread..\超鏈接文件\圖10.1.1.doc《Java程序設計》課程10.2線程的實現在MyThread類中定義了私有的成員變量ticket,表示當前還剩余的票數,,并重寫了父類中的run()方法;(2)再定義一個功能測試類ThreadTest4,在本類的main()方法中定義3個MyThread類的對象,并調用實例對象的start()方法,代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果如圖10.7所示。圖10.7案例1-5的運行結果《Java程序設計》課程10.2線程的實現以上程序通過Thread類實現多線程,程序中啟動了3個線程,但是這3個線程卻分別賣了各自的5張票,并沒有達到資源共享的目的。注意:為什么會出現這種情況呢?其原因是mt1,mt2,mt3是MyThread類的實例對象,所以每個實例對象都有自己的一份實例變量ticket。《Java程序設計》課程10.2線程的實現10.2.3項目實施項目10-1:模擬多線程賣票程序(車票作為共享資源的方式)。(1)分析通過分析題目可以用以下幾個步驟實現本項目:①定義實現Runnable接口的MyThread類,在類中定義屬性、構造方法,并重寫Runnable接口的run()方法;②定義測試類RunnableTest2,測試程序的運行結果。(2)編碼:①定義實現Runnable接口的MyThread類,代碼如下:..\超鏈接文件\圖10.1.1.doc②定義測試類RunnableTest2,測試程序的運行結果,代碼如下:..\超鏈接文件\圖10.1.1.doc重點:Threadt1=newThread(mt,"線程A");//實例化線程對象。在t1線程的構造方法中,第一個參數是實現了Runnable接口的類的實例對象,第二個參數是線程的名字。在線程功能類MyThread類的run()方法中,可以通過Thread.currentThread().getName()這個方法來得到當前線程的名字?!禞ava程序設計》課程10.2線程的實現(3)調試運行,顯示結果該程序的運行結果如圖10.8所示:圖10.8項目10-1的運行結果從程序的運行結果可以清楚地發(fā)現,雖然啟動了3個線程,但是每個線程一共只賣了5張車票,即線程功能類中的ticket屬性被所有的線程對象共享??梢姡簩崿FRunnable接口相對于繼承Thread類來說,有如下顯著的優(yōu)勢:..\超鏈接文件\圖10.1.1.doc《Java程序設計》課程10.2線程的實現注意:實現Runnable接口相對于繼承Thread類,實現多線程的優(yōu)勢:(1)適合多個相同程序代碼的線程去處理同一資源的情況。(2)可以避免由于Java的單繼承特性帶來的局限。(3)增強了程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的。所以,在開發(fā)中建議大家使用Runnable接口實現多線程,本書后面的例子也都采用Runnable接口的方式實現多線程操作?!禞ava程序設計》課程10.2線程的實現10.2.4能力拓展(1)選擇題①假設已經編好了一個線程類MyThread,要在main()中啟動該線程,需使用以下哪個方法(
)A.newMyThread();B.MyThreadmyThread=newMyThread();myThread.start();C.MyThreadmyThread=newMyThread();myThread.run();D.newMyThread.start();②編寫線程類,要繼承的父類是(
)A.ObjectB.RunnableC.SerializableD.Thread③編寫線程類,可以通過實現那個接口來實現(
)A:RunnableB:ThrowableC:SerializableD:Comparable④以下方法用于定義線程執(zhí)行體的是(
)A.start()B.init()C.run()D.main()B
DAC《Java程序設計》課程10.2線程的實現(2)填空題①在Java語言中,定義線程類可以通過繼承(
)類來實現。②在Java語言中,定義線程功能類可以通過實現(
)接口來實現。③線程的主體功能的實現,是通過重寫Thread類的(
)方法,或者通過重寫RunnableJ接口中的(
)方法來完成的。(3)編程①編程實現本課中的案例10-1至10-5的功能,并運行測試。②編程實現用多線程的方式模擬賣車票的功能,假設票數為20張,有5個電話同時搶票。③編程實現本節(jié)課中的項目10-1中的功能,并運行測試。ThreadRunnablerun()run()《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法10.3.1項目(10-2)描述測試線程的優(yōu)先級。10.3.2項目知識準備(1)線程的狀態(tài)要想實現多線程,必須在主線程中創(chuàng)建新的線程對象。任何線程一般具有5種狀態(tài),這5種狀態(tài)分別是創(chuàng)建狀態(tài)、就緒狀態(tài)、運行狀態(tài)、阻塞狀態(tài)、終止狀態(tài)。線程狀態(tài)的轉換與方法之間的關系可以用圖10.9來表示:圖10.9線程狀態(tài)轉換圖《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法①創(chuàng)建狀態(tài)在程序中使用構造方法創(chuàng)建了一個線程對象后,新的線程對象便處于新建狀態(tài),此時,它已經有了相應的內存空間和其他資源,但還處于不可運行狀態(tài)。新建一個線程對象可以采用Thread類的構造方法來實現,比如“Threadt=newThread();”。②就緒狀態(tài)新建線程對象后,如果調用了該線程對象的start()方法后,則該線程就啟動了,當線程啟動時,線程就進入了就緒狀態(tài)。此時,線程就進入了線程隊列排隊,等待CPU的調度,這表明線程具備了運行條件。③運行狀態(tài)當就緒狀態(tài)的線程被調用并獲得處理器的調度時,線程就進入了運行狀態(tài)。此時,系統(tǒng)會自動調用該線程對象的run()方法,run()方法中定義了該線程的操作和功能?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法④阻塞狀態(tài)一個正在運行的線程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時的輸入/輸出操作時,就讓出CPU并暫時中止自己的執(zhí)行,進入阻塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調用sleep()、suspend()、wait()等方法,線程都將進入阻塞狀態(tài)。阻塞時,線程不鞥你進入排隊隊列,只有當引起阻塞的原因被消除后,線程才可以進入就緒狀態(tài),等待CPU的召喚。⑤終止狀態(tài)終止狀態(tài),也成為死亡狀態(tài)。線程調用stop()方法時或run()方法執(zhí)行結束后,線程即處于終止狀態(tài)。處于終止狀態(tài)的線程不具有繼續(xù)運行的能力?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法(2)線程操作的常用方法通過前面的講解,可以發(fā)現在Java實現多線程的程序中,雖然Thread類實現了Runnable接口,但是操作線程的主要方法并不在Runnable接口中,而是在Thread類中,表10-1列出了Thread類中的常用方法。下面介紹一下常用的線程操作方法。①取得和設置線程名稱在Thread類中可以通過getName()方法取得線程的名稱,還可以通過setName()方法設置線程的名稱。線程的名稱一般在啟動線程前設置,但也允許為已經運行的線程設置名稱。允許兩個Thread對象有相同的名稱,但應該盡量避免出現這樣的情況發(fā)生。注意:如果沒有設置線程的名稱,系統(tǒng)會為其自動分配名稱。在線程操作中,如果沒有為一個線程指定一個名稱,則系統(tǒng)在使用時會為線程分配一個名稱,名稱的格式為Thread-Xx?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法案例10-6:取得和設置線程的名稱。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了Thread.currentThread()方法得到了當前正在運行的線程,并調用了該線程對象的getName()方法獲取了線程對象的名稱。(2)編寫測試類ThreadNameTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果如圖10.10所示:《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法圖10.10案例10-6的運行結果從程序的運行結果中可以發(fā)現,沒有設置線程名稱的3個線程的名稱都是很有規(guī)律的,分別為Thread-0、Thread-1、Thread-2。下面再來觀察一下案例10-7這個例子?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法案例10-7:測試當前線程的名稱。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了Thread.currentThread()方法得到了當前正在運行的線程,并調用了該線程對象的getName()方法獲取了線程對象的名稱。(2)編寫測試類ThreadNameTest2類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果如圖10.11所示:圖10.11案例10-7的運行結果《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法在上面的程序中,主方法直接通過Runnable接口的子類對象調用其中的run()方法,另外一個是通過線程對象調用start()方法啟動的,從結果中發(fā)現,主方法實際上也是一個線程。
提醒:在Java中所有的線程都是同時啟動的,哪個線程先搶到了CPU資源,哪個線程就先運行。注意:Java程序每次運行至少啟動2個線程。這是因為每當使用Java命令執(zhí)行一個類時,實際上都會啟動一個JVM(Java虛擬機),每一個JVM實際上就是在操作系統(tǒng)中啟動了一個進程,Java本身具備了垃圾的收集機制。所以在Java運行時至少會啟動兩個線程,一個是main線程,另外一個是垃圾收集線程。②判斷線程是否啟動通過前面的介紹知道,通過Thread類中的start()方法通知CPU這個線程已經準備好啟動,然后就等待分配CPU資源,運行此線程。在Java中可以使用isAlive()方法測試線程是否啟動而且仍然在啟動。《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法案例10-8:測試線程是否處于活動狀態(tài)。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了Thread.currentThread()方法得到了當前正在運行的線程,并調用了該線程對象的getName()方法獲取了線程對象的名稱。(2)編寫測試類ThreadIsAliveTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc(3)測試運行,顯示結果如圖10.12所示:圖10.12案例10-8的運行結果《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法注意:主線程有可能比其他線程先執(zhí)行完。因為線程操作的不確定性,所以主線程有可能最先執(zhí)行完,那么此時其他線程不會受到任何影響,并不會隨著主線程的結束而結束。③線程的強制運行在線程操作中,可以使用join()方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之后才可以繼續(xù)執(zhí)行。案例10-9:測試線程的強制運行。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了Thread.currentThread()方法得到了當前正在運行的線程,并調用了該線程對象的getName()方法獲取了線程對象的名稱?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法(2)編寫測試類ThreadJoinTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.13所示:圖10.13案例10-9的運行結果上面的程序,多運行幾次,通過觀察結果可以發(fā)現:當主線程中的i的值超過2時,一定會先強制執(zhí)行線程t1,當t1線程執(zhí)行完畢后,主線程才會繼續(xù)向后執(zhí)行。即主線程必須等待這個線程完成之后才會繼續(xù)執(zhí)行。④線程的休眠在Java程序中允許一個線程進行暫時的休眠,直接使用Thread.sleep()方法即可實現休眠?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法案例10-10:測試線程的休眠。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了線程休眠方法Thread.sleep()方法,也調用Thread.currentThread()方法得到了當前正在運行的線程,并調用了該線程對象的getName()方法獲取了線程對象的名稱。(2)編寫測試類ThreadSleepTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.14所示:上面的程序執(zhí)行時,每次的輸出都是間隔了1000毫秒,達到了延時操作的效果。圖10.14案例10-10的運行結果《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法⑤線程的中斷當一個線程運行時,另外一個線程可以直接通過interrupt()方法中斷其運行狀態(tài)。案例10-11:測試線程的中斷。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了線程休眠方法Thread.sleep()方法,并對休眠方法做了異常捕獲處理。(2)編寫測試類ThreadInterruptTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.15所示:圖10.15案例10-11的運行結果從上面的程序可以看出,一個線程啟動后進入了休眠狀態(tài),原本是要休眠10秒之后再繼續(xù)執(zhí)行,但是主方法是線程啟動之后的2秒后就將其中斷,休眠一旦中斷之后將執(zhí)行catch中的代碼?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法⑥后臺線程(守護線程)在Java程序中,只要前臺有一個線程在運行,則整個java進程都不會消失,所以此時可以設置一個后臺線程,也叫守護線程,這樣即使java進程結束了,此后臺線程依然會繼續(xù)運行。要想實現這樣的操作,直接使用setDaemon()方法就可以實現。案例10-12:測試后臺線程的設置。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了線程休眠方法Thread.sleep()方法,并對休眠方法做了異常捕獲處理。(2)編寫測試類ThreadDaemonTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.16所示:《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法圖10.16案例10-12的運行結果在上面的程序中,在線程類MyThread中,盡管run()方法中是死循環(huán)的方式,但是程序仍然可以執(zhí)行完,因為該線程是后臺線程,也叫守護線程。《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法提醒:在Java中線程分為兩種類型,用戶線程和守護線程。通過線程對象的setDaemon(false)設置為用戶線程;通過線程對象的setDaemon(true)設置為守護線程。如果不設置,默認值為用戶線程。注意:用戶線程和守護線程的區(qū)別:(1)主線程結束后,用戶線程還會繼續(xù)運行,JVM(Java虛擬機)存活;主線程結束后,守護線程和JVM的狀態(tài)由下面的第(2)條確定。(2)如果沒有用戶線程,都是守護線程,那么JVM結束(隨之而來的是所有一切都煙消云散,包括所有的守護線程)。舉例說明:垃圾回收線程就是一個經典的守護線程,當程序中不再有任何運行的Thread,程序就不會再產生垃圾,垃圾回收線程也就無事可做,所以當垃圾回收線程是JVM上僅剩的線程時,垃圾回收線程會自動離開。它始終在低級別的狀態(tài)中運行,用于實時監(jiān)控和管理系統(tǒng)中的可回收資源?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法⑦線程的禮讓在線程操作中,可以使用yield()方法將一個線程的操作暫時讓給其他線程執(zhí)行。下面通過案例10-13來演示線程的禮讓。案例10-13:測試線程的禮讓。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中當i==3時調用了線程的禮讓方法。(2)編寫測試類ThreadYieldTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.17所示:圖10.17案例10-13的運行結果《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法⑧線程的優(yōu)先級在Java程序的線程操作中,所有的線程在運行前都會保持在就緒狀態(tài),那么此時,哪個線程的優(yōu)先級高,哪個線程就有可能會先執(zhí)行。在Java的線程中可以使用setPriority()方法設置一個線程的優(yōu)先級,在Java的線程中一共有3種優(yōu)先級,如表10.2所示。序號定義描述表示的常量1publicstaticfinalintMIN_PRIORITY最低優(yōu)先級12publicstaticfinalintNORM_PRIORITY中等優(yōu)先級,默認53publicstaticfinalintMAX_PRIORITY最高優(yōu)先級10表10.2線程的優(yōu)先級《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法10.3.3項目實施(1)通過分析項目10-2,可以用以下幾個步驟實現本項目:①定義一個實現Runnable接口的線程功能類MyThread,在類中重寫接口中的run()方法;②定義測試類ThreadPriorityTest,測試程序的運行結果;。(2)編碼:①先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了線程休眠方法Thread.sleep()方法,并對休眠方法做了異常捕獲處理。(2)編寫測試類ThreadPriorityTest類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.18所示:《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法圖10.18項目10-2的運行結果從上面的運行結果中可以發(fā)現,線程將根據其優(yōu)先級的大小來決定哪個線程會先運行,但是運行多次后,也會發(fā)現并非線程的優(yōu)先級越高就一定會先執(zhí)行,哪個線程先執(zhí)行將由CPU的調度來決定。提醒:main線程的優(yōu)先級是NORM_PRIORITY??梢酝ㄟ^下面的程序段來驗證?!禞ava程序設計》課程10.3線程的狀態(tài)與線程常用方法10.3.4能力拓展(1)選擇題①4.當哪個方法結束時,能使線程進入死亡狀態(tài)。(
)A.runB.setProrityC.yieldD.sleep②以下哪個方法不能使線程進入阻塞狀態(tài)(
)A.sleep()B.wait()C.suspend()D.stop()③用哪個方法可以改變線程的優(yōu)先級。(
)A.runB.setProrityC.yieldD.sleepAD
B《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法④用哪個方法可以實現線程的禮讓。(
)A.runB.setProrityC.yieldD.sleep⑤線程通過哪個方法可以休眠一段時間,然后恢復運行。(
)A.runB.setProrityC.yieldD.sleep⑥方法resume()負責重新開始哪種線程的執(zhí)行。(
)A.被stop()方法停止B.被sleep()方法停止C.被wait()方法停止D.被suspend()方法停止
CDD《Java程序設計》課程10.3線程的狀態(tài)與線程常用方法(2)填空題①線程有5種狀態(tài),它們是(
)、(
)、(
)、(
)和(
)等狀態(tài)。②新創(chuàng)建的線程默認的優(yōu)先級是(
)。③要實現線程的強制執(zhí)行,需要使用(
)方法。④要實現線程的禮讓,需要使用(
)方法。⑤要實現線程的休眠,需要使用(
)方法。⑥獲取當前線程的名稱,可以使用(
)方法,設置線程的名稱,可以使用setName()方法。⑦獲取線程的優(yōu)先級,可以使用(
)方法,設置線程的優(yōu)先級可以使用(
)方法。(3)編程①編程實現本章節(jié)中的案例10-6至案例10-13的編碼。②實現本章節(jié)中項目10-2的功能,并運行測試。③設計四個線程對象對同一個數據進行操作,兩個線程執(zhí)行減操作,兩個線程執(zhí)行加操作。就緒
創(chuàng)建
運行
阻塞
死亡
NORM_PRIORITYjoin()
yield()
sleep()
getName()getPriority()setPriority()
《Java程序設計》課程10.4同步與鎖死10.4.1項目(10-3)描述編程演示多線程同步時出現的死鎖現象。10.4.2項目知識準備一個多線程的程序如果是通過Runnable接口實現的,則意味著類中的屬性可以被多個線程共享,那么這樣就會造成一個問題,如果這些多個線程要操作同一資源時就有可能出現資源的同步問題。例如,前面項目10-1的賣票程序,如果多個線程同時操作時,就有可能出現賣出的票為負數的問題。
(1)問題的引出通過實現Runnable接口的方式實現多線程,并產生3個線程對象,同時賣5張票。案例10-14:測試賣票程序存在問題。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中實現了每間隔0.5,就賣出一張票。(2)編寫測試類SynchronizedTest1類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
《Java程序設計》課程10.4同步與鎖死(3)測試運行,顯示結果如圖10.19所示:圖10.19案例10-14的運行結果從程序的運行結果中可以發(fā)現,程序中加入了延遲操作,所以在運行的最后出現了負數的情況,那么為什么會產生這樣的問題呢?從上面的操作代碼中可以發(fā)現對于票數的操作步驟如下:(1)判斷票數是否大于0,大于0則表示還有票可以賣;(2)如果票大于0,則將票賣出?!禞ava程序設計》課程10.4同步與鎖死但是,在上面的操作代碼中,在步驟(1)和步驟(2)之間加入了延遲操作,那么一個線程就有可能在還沒有對票數進行減操作之前,其他線程就已經將票數減少了,這樣就會出現票數為負數的情況。如圖10.20所示圖10.20案例10-14程序操作示意圖《Java程序設計》課程10.4同步與鎖死提醒:線程安全問題產生的原因:(1)多個線程同時操作共享數據;(2)操作共享數據的線程代碼有多條。(即有兩條或兩條以上)當一個線程在執(zhí)行操作共享數據的多條代碼過程中,其他線程參與了運算,這樣就會導致線程安全問題的產生。(2)使用同步解決問題解決上面問題的思路是:將多條操作共享數據的線程代碼封裝起來,當有線程在執(zhí)行這些代碼的時候,其他線程是不可以參與運算的。必須要等待當前線程把這些代碼全部執(zhí)行完畢后,其他線程才可以參與運算。解決資源共享的同步問題,可以使用同步代碼塊和同步方法兩種方式完成。①同步代碼塊在前面幾章中已經為大家介紹過代碼塊的概念,所謂代碼塊就是指使用“{}”括起來的一段代碼,根據其位置和聲明的不同,可以分為普通代碼塊、構造代碼塊、靜態(tài)代碼塊3種,如果在代碼塊上加上synchronized關鍵字,則此代碼塊就稱為同步代碼塊。通過代碼塊的格式如下。從上面的格式中可以發(fā)現,在使用同步代碼塊時必須制定一個需要同步的對象,但一般都將當前對象(this)設置為同步對象。《Java程序設計》課程10.4同步與鎖死案例10-15:測試賣票程序存在問題。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中實現了使用同步代碼塊賣票。(2)編寫測試類SynchronizedTest2類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.21所示:圖10.21案例10-15的運行結果從程序的運行結果中可以發(fā)現,以上代碼將取值和修改值這兩步操作進行了同步,所以就不會再出現賣票混亂和出現負數的情況了。②同步方法除了可以將需要的同步代碼設置為同步代碼塊之外,還可以使用synchronized關鍵字將一個方法聲明為同步方法,同步方法格式如下所示?!禞ava程序設計》課程10.4同步與鎖死案例10-16:使用同步方法解決線程同步問題。(1)先定義一個實現Runnable接口的線程功能類MyThread,代碼如下:..\超鏈接文件\圖10.1.1.doc
在MyThread類中重寫了Runnable接口中的run(),并在run()方法中調用了同步方法sale(),在同步方法sale()中實現了賣票功能。(2)編寫測試類SynchronizedTest2類,測試程序運行效果。測試類代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)測試運行,顯示結果如圖10.22所示:從程序的運行結果中可以發(fā)現,同步方法也完成了與同步代碼塊同樣的功能。圖10.22案例10-16的運行結果《Java程序設計》課程10.4同步與鎖死提醒:同步的優(yōu)缺點:(1)同步的好處:解決了線程安全問題。(2)同步的弊端:相對降低了效率,因為同步外的線程都會判斷同步鎖。同步的前提:同步中必須有多個線程并使用同一個鎖。注意:同步方法和同步代碼塊的區(qū)別:(1)同步方法的鎖是固定的this。(2)同步代碼塊的鎖是任意的對象。建議使用同步代碼塊。提醒:同步能解決問題的前提是:多個線程必須使用同一個鎖,才能用同步解決線程共享資源混亂問題?!禞ava程序設計》課程10.4同步與鎖死(3)死鎖同步可以保證資源共享操作的正確性,但是過多同步也會產生問題,如圖10.23所示。例如:現在有張三想要李四的F-22模型,李四想要張三的J-20模型。此時張三對李四說:“把你的F-22模型給我,我就給你J-20模型”,李四也對張三說:“把你的J-20模型給我,我就給你F-22模型”,此時張三在等待李四的答復,而李四也在等待張三的答復,那么這樣下去最終結果就是:張三得不到李四的F-22模型,李四也得不到張三的J-20模型,這實際上就是死鎖的概念。圖10.23同步產生的問題所謂死鎖就是指兩個線程都在等待彼此完成任務,造成了程序的停滯,一般程序的死鎖都是在程序運行時出現的。下面通過項目10-3這個例子來觀察出現死鎖的情況?!禞ava程序設計》課程10.4同步與鎖死10.4.3項目實施(1)分析通過分析題目可以用以下幾個步驟實現本項目:①先定義一個包ject3;②在包中定義一個普通類ZhangSan,在類中定義兩個方法,一個是說方法say(),一個是得到方法get();③在包中定義一個普通類LiSi,在類中定義兩個方法,一個是說方法say(),一個是得到方法get();④在包中定義線程功能類ThreadDeadLock,并在類中定義兩個static的鎖,并重寫run()方法,在run()方法中,根據標識flag的不同,執(zhí)行不同的操作。④在包中定義測試類ThreadDeadLockTest,測試程序的運行結果?!禞ava程序設計》課程10.4同步與鎖死(2)編碼:①在項目中創(chuàng)建一個包ject3;②在包ject3中定義普通類類ZhangSan,代碼如下:..\超鏈接文件\圖10.1.1.doc
③在包ject3中定義普通類類LiSi,代碼如下:..\超鏈接文件\圖10.1.1.doc
④在包ject3中定義實現了Runnable接口的線程功能類MyThread,代碼如下:⑤定義測試類ThreadDeadLockTest,測試程序的運行結果,代碼如下:..\超鏈接文件\圖10.1.1.doc
(3)調試運行,顯示結果該程序的運行結果如圖10.24所示:從程序的運行結果中可以發(fā)現,兩個線程都在等待對方執(zhí)行完成,釋放鎖;這樣程序就無法向下繼續(xù)執(zhí)行,從而造成了死鎖的現象。圖10.24項目10-3的運行結果《Java程序設計》課程10.4同步與鎖死提醒:關于同步和死鎖:多個線程共享同一資源,并且操作資源的代碼多于2個時,需要同步以保證資源操作的完整性和安全性;但是過多的同步就有可能產生死鎖。10.4.4能力拓展(1)選擇題①用哪個關鍵字可以實現線程的同步操作。(
)A.waitB.synchronizedC.yieldD.sleepB(2)填空題①在Java語言中,使用(
)關鍵字可以實現線程的同步操作。②在Java中,使用synchronized關鍵字實現線程的同步有兩種方式,一種是(
),另一種是同步方法。synchronized同步代碼塊《Java程序設計》課程10.4同步與鎖死(3)編程①實現本章中案例10-14到案例10-16,并運行測試。②實現本章中的項目10-3,并運行測試。③編寫線程同步模擬應用程序:<1>大氣環(huán)境數據為:溫度,濕度,風速。<2>一個大氣環(huán)境傳感器測量環(huán)境數據需要5秒時間。<3>一個計算機讀取傳感器的環(huán)境數據需要0.01秒時間。
模擬一個計算機讀取大氣環(huán)境傳感器的讀取的隨機的溫度、濕度和風速,讀取100次?!禞ava程序設計》課程10.5生產者與消費者模型10.5.1項目(10-4)描述編程演示生產者消費者模型。10.5.2項目知識準備在線程操作中有一個經典的案例,這就是生產者和消費者模型,生產者不斷生產,消費者不斷取走生產者生產的產品。如圖10.25所示。圖10.25生產者和消費者模型示意圖《Java程序設計》課程10.5生產者與消費者模型在圖10.25中可以知道,生產者生產出信息后將其放到一個容器之中,消費者從此容器中取出數據,但是在本程序中因為涉及到線程被CPU調用的不確定性,所以會存在以下2個問題:①假設生產者線程剛剛向共享的數據中添加了信息的書名,還沒來得及加入作者的內容,程序就切換到了消費者線程,消費者線程把作者信息和上一條書名信息聯系到一起了,即會出現信息的錯位。②生產者生產了多個數據,消費者才開始取數據,不能做到生產一個,馬上消費一個的目的。(1)程序的初步實現因為程序中生產者不斷生產的是信息,而消費者不斷取出的也是信息,所以定義一個圖書信息的類Book.java。再并分別定義生產者類和消費者類,最后定義一個測試類。如案例10-17所示。案例10-17:生產者和消費者模型程序的初步實現。(1)先定義信息類Book,代碼如下:..\超鏈接文件\圖10.1.1.doc
Book類的組成非常簡單,只包含了用戶保存圖書名稱信息的bookName屬性和用于保存作者信息的author屬性。因為生產者和消費者要操作的是共享數據,所以生產者和消費者分別實現Runnable接口,并接收Book類的實例。(2)編寫生產者類Producer,代碼如下:..\超鏈接文件\圖10.1.1.doc
《Java程序設計》課程10.5生產者與消費者模型在生產者Producer的構造方法中傳入了Book類的實例對象,是為了達到讓生產者和消費者共享數據的目的。然后再run()方法中循環(huán)了10次生產圖書信息,另外,為了讓大家更容易發(fā)現問題,還在run()方法中加入了延遲操作。(Thread.sleep())。(3)編寫生產者類Producer,代碼如下:..\超鏈接文件\圖10.1.1.doc
在消費者Consumer的構造方法中傳入了Book類的實例對象,是為了達到讓生產者和消費者共享數據的目的。然后再run()方法中循環(huán)了10次取出圖書信息。(4)編寫測試類ProducerConsumerTest1,測試程序的運行結果,代碼如下:..\超鏈接文件\圖10.1.1.doc
(5)測試運行,顯示結果如圖10.26所示:圖10.26案例10-17的運行結果《Java程序設計》課程10.5生產者與消費者模型從程序的運行結果中可以發(fā)現,輸出的結果中出現了上面提到的2種問題情況,即出現了①假設生產者線程剛剛向共享的數據中添加了信息的書名,還沒來得及加入作者的內容,程序就切換到了消費者線程;還有消費者線程把作者信息和上一條書名信息聯系到一起了,即會出現信息的錯位。②生產者生產了多個數據,消費者才開始取數據,不能做到生產一個,馬上消費一個的目的。下面,我們先來解決第一個問題,第一個問題需要同步就可以解決,即生產者線程或消費者線程的操作是一個整體,必須全部操作完成后,才能交給下一個線程繼續(xù)處理。(2)使用同步解決第一種存在的問題如果要為操作加入同步,則可以通過定義同步方法的方式完成,則將設置書名屬性和設置作者屬性定義成一個同步方法。這樣Book類,Producer類,Consumer類都要做出改進,具體詳見案例10-18。案例10-18:生產者和消費者模型加入同步后的程序實現。(1)先定義共享資源類Book,代碼如下..\超鏈
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024-2025學年八年級下學期開學水平調研生物試題
- 私人房產附屬設施買賣合同
- 清關代理合同協議書
- 基于情境學習的數學邏輯思維培養(yǎng)教學方案
- 智能化產業(yè)園區(qū)管理平臺合作協議
- 智能家居產品研發(fā)及銷售協議
- 電子商務退換貨免責條款
- 超市食材進銷存協議
- 混凝土水泥買賣合同
- 自來水管理承包合同
- 智慧漁政網格管理平臺項目方案
- GB/T 7716-2024聚合級丙烯
- 《弱電知識培訓》課件
- 丹麥地理課件
- 住宅小區(qū)供配電設施建設和改造技術標準
- 勞動合同(模版)4篇
- 100道公安基礎知識題目訓練含答案
- 2024年重慶市中考道德與法治試卷(AB合卷)附答案
- 口腔耗材采購合同范本
- JBT 14682-2024 多關節(jié)機器人用伺服電動機技術規(guī)范(正式版)
- 胃腸鏡健康宣教胃腸鏡檢查注意事項適應癥與禁忌癥宣傳課件
評論
0/150
提交評論