




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
第13章多線程01開篇引導(dǎo)故事:廚房里的廚師們,在一個繁忙的餐廳里,有一個大廚房,里面有幾位廚師正緊張而有序地工作著。這個廚房就像是一個多線程的Java程序,每位廚師都像是一個線程,他們各自負(fù)責(zé)不同的任務(wù),但共同協(xié)作以完成整個晚餐的服務(wù)。角色與任務(wù):主廚(MainChef):相當(dāng)于Java程序的主線程,他負(fù)責(zé)總體協(xié)調(diào),比如分配任務(wù)給各位廚師,以及監(jiān)控整個廚房的運作。切菜師(VegetableChopper):一個專門的線程,負(fù)責(zé)將所有蔬菜切成需要的形狀和大小。他快速地工作,不斷從蔬菜籃中取出食材,完成切割后放在準(zhǔn)備好的盤子里。廚師A(ChefA):另一個線程,專門負(fù)責(zé)烹飪?nèi)忸悺Kǜ鞣N肉類料理,從牛排到烤雞,都能得心應(yīng)手。他時刻關(guān)注著火爐,確保每一道菜都能達(dá)到最佳口感。廚師B(ChefB):又一個線程,負(fù)責(zé)制作面食和主食。他忙著揉面團(tuán)、搟面片、包餃子,為客人準(zhǔn)備美味的碳水化合物。裝盤師(Plater):這是一個輔助線程,他的工作是在所有食材準(zhǔn)備好之后,將它們精心擺放在盤子上,裝飾成令人垂涎的佳肴。他確保每一道菜上桌時都是最美的狀態(tài)。同步與協(xié)作:共享資源:廚房中的火爐、刀具、案板等都是共享資源。廚師們需要輪流使用這些資源,避免沖突。這就像是Java中的同步代碼塊,確保同一時間只有一個線程可以訪問某個共享資源。等待與通知:當(dāng)某個廚師需要等待某種食材時(比如切菜師還沒切好胡蘿卜),他就會暫時停下手中的工作,直到食材準(zhǔn)備好。這類似于Java中的wait()和notify()機(jī)制,線程在特定條件下等待或喚醒。協(xié)作完成:雖然每位廚師都有自己的專長和任務(wù),但他們必須緊密協(xié)作,才能確保晚餐服務(wù)順利進(jìn)行。這就像Java多線程程序中的各個線程,雖然各自獨立執(zhí)行,但通過共享數(shù)據(jù)和協(xié)調(diào)機(jī)制,共同完成任務(wù)。01開篇引導(dǎo)結(jié)尾:隨著夜幕降臨,餐廳逐漸熱鬧起來。廚房里的廚師們忙碌而有序地工作著,一道道美味佳肴被送到客人面前。這一切的順利進(jìn)行,都要歸功于他們(線程)之間的默契協(xié)作和高效管理。通過這個小故事,我們可以更直觀地理解Java多線程的概念和重要性。在Java程序中,合理地使用多線程可以顯著提高程序的執(zhí)行效率和響應(yīng)速度,但也需要注意線程間的同步和協(xié)作問題,以避免數(shù)據(jù)錯亂和程序崩潰的風(fēng)險。01開篇引導(dǎo)知識要點掌握程度相關(guān)知識線程的基本概念了解程序、進(jìn)程與線程多線程的優(yōu)點單核與多核的概述4.并行與并發(fā)的概述線程的創(chuàng)建與啟動掌握繼承Therad類2.實現(xiàn)Runnable接口3.匿名內(nèi)部類創(chuàng)建啟動線程4.繼承Thread類和實現(xiàn)Runnable接口區(qū)別線程的生命周期重點掌握線程的生命周期多線程同步重點掌握資源線程安全問題2.同步機(jī)制線程的通信重點掌握為什么要通信2.等待喚醒機(jī)制3.線程池JDK5.0新增線程創(chuàng)建方式掌握實現(xiàn)Callable接口2.使用線程池01開篇引導(dǎo)技能要點掌握程度應(yīng)用方向線程同步重點掌握應(yīng)用開發(fā)Web開發(fā)桌面開發(fā)大數(shù)據(jù)開發(fā)線程的通信重點掌握應(yīng)用開發(fā)Web開發(fā)桌面開發(fā)
4.大數(shù)據(jù)開發(fā)JDK5.0新增線程的創(chuàng)建方式掌握應(yīng)用開發(fā)Web開發(fā)桌面開發(fā)
4.大數(shù)據(jù)開發(fā)多線程基本概念-程序、進(jìn)程和線程021.程序的概述
為完成特定任務(wù),用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼,靜態(tài)對象。2.進(jìn)程的概述(1)
程序的一次執(zhí)行過程,可視為內(nèi)存中正在運行的應(yīng)用程序?qū)嵗?,如運行中的QQ或網(wǎng)易音樂播放器。每個進(jìn)程均擁有其獨立的內(nèi)存空間,并經(jīng)歷從創(chuàng)建、運行到消亡的完整生命周期。(2)程序本身是靜態(tài)的代碼集合,而進(jìn)程則是其動態(tài)執(zhí)行的體現(xiàn)。作為操作系統(tǒng)進(jìn)行資源調(diào)度和分配的最小單位(亦為系統(tǒng)運行程序的基本單位),進(jìn)程在運行期間會被系統(tǒng)分配獨立的內(nèi)存區(qū)域。(3)現(xiàn)代操作系統(tǒng)普遍支持多進(jìn)程環(huán)境,允許多個程序同時運行。例如,在上課期間,我們可能同時啟動編輯器、錄屏軟件、畫圖板以及DOS窗口等多個軟件程序,每個程序都以獨立進(jìn)程的形式在系統(tǒng)中運行。多線程基本概念-程序、進(jìn)程和線程023.線程的概述(1)
進(jìn)程可進(jìn)一步被細(xì)化為線程,即程序內(nèi)部的一條獨立執(zhí)行路徑。每個進(jìn)程都至少包含一個線程。當(dāng)一個進(jìn)程能夠同時并行執(zhí)行多個線程時,我們稱之為支持多線程處理。線程作為CPU調(diào)度和執(zhí)行的最小基本單位,其運行效率至關(guān)重要。(2)一個進(jìn)程內(nèi)的多個線程共享相同的內(nèi)存空間,它們從同一堆內(nèi)存中分配對象,并可以相互訪問相同的變量和對象。這種共享機(jī)制極大地簡化了線程間的通信過程,提升了通信效率。然而,多個線程對共享系統(tǒng)資源的操作也可能引發(fā)安全隱患,需要開發(fā)者在設(shè)計時予以充分考慮。小貼士:不同的進(jìn)程之間是不共享內(nèi)存的。進(jìn)程之間的數(shù)據(jù)交換和通信的成本很高。多線程基本概念-線程的調(diào)度02(1)分時調(diào)度所有線程按照既定規(guī)則輪流獲得CPU的使用權(quán),確保每個線程都能平均分配到相應(yīng)的CPU時間片。(2)搶占式調(diào)度在搶占式調(diào)度中,具有較高優(yōu)先級的線程擁有更大的機(jī)會優(yōu)先獲得CPU資源。若存在多個優(yōu)先級相同的線程,系統(tǒng)將隨機(jī)選擇其中一個進(jìn)行執(zhí)行,體現(xiàn)了線程調(diào)度的隨機(jī)性。值得注意的是,Java虛擬機(jī)正是采用了這種搶占式調(diào)度策略。多線程基本概念-線程的優(yōu)點02(1)提高應(yīng)用程序的響應(yīng)速度。這一優(yōu)化對圖形化界面尤為重要,能顯著提升用戶體驗。(2)提升計算機(jī)系統(tǒng)CPU的利用效率。(3)優(yōu)化程序結(jié)構(gòu)。將冗長且復(fù)雜的進(jìn)程合理劃分為多個線程,使其能夠獨立運行,這樣的設(shè)計既便于理解又易于修改。多線程的基本概念-單核與多核的概述021.單核的概述在一個時間單元內(nèi),只能執(zhí)行一個線程的任務(wù)。例如,可以把CPU看成是醫(yī)院的醫(yī)生診室,在一定時間內(nèi)只能給一個病人診斷治療。所以單核CPU就是,代碼經(jīng)過前面一系列的前導(dǎo)操作(類似于醫(yī)院掛號,比如有10個窗口掛號),然后到cpu處執(zhí)行時發(fā)現(xiàn),就只有一個CPU(對應(yīng)一個醫(yī)生),大家排隊執(zhí)行。2.多核的概述每個核心都具備完整的處理器功能,包括算術(shù)邏輯單元(ALU)、控制單元、寄存器等。這些核心可以并行地執(zhí)行指令,就如同多個獨立的處理器在協(xié)同工作。例如,在一個四核處理器中,就相當(dāng)于有四個獨立的處理器在同一芯片上同時運行。多線程的基本概念-并行與并發(fā)021.并行的概述同時發(fā)生:描述兩個或多個事件在同一時間點共同發(fā)生的情境。此概念亦適用于技術(shù)領(lǐng)域,如在同一時刻,多條指令能夠在多個CPU上并行執(zhí)行,極大地提升了處理效率。類比于日常生活中,多個人在同一時間內(nèi)各自進(jìn)行不同的活動,體現(xiàn)了時間與資源的有效利用。2.并發(fā)的概述兩個或多個事件在同一時間段內(nèi)并行發(fā)生,即在特定的時間范圍內(nèi),多個指令在單個CPU上實現(xiàn)快速切換與交替執(zhí)行,從而在宏觀上營造出多個進(jìn)程同步運行的效果。線程創(chuàng)建與啟動-繼承Threrad類03Java通過繼承Thread類來創(chuàng)建并啟動多線程,實現(xiàn)流程為:(1)定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務(wù)。(2)創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象。(3)調(diào)用線程對象的start()方法來啟動該線程?!纠?3-1】繼承Thread類,創(chuàng)建線程并啟動線程,代碼如下:packagecom.chapter13.create;publicclassMyThreadDemoextendsThread{//定義指定線程名稱的構(gòu)造方法publicMyThreadDemo(Stringname){//調(diào)用父類的String參數(shù)的構(gòu)造方法,指定線程的名稱super(name);}publicvoidrun(){for(inti=0;i<10;i++){System.out.println(getName()+":正在執(zhí)行!"+i);}}}線程創(chuàng)建與啟動-繼承Threrad類03packagecom.chapter13.create;publicclassThreadDemoTest{publicstaticvoidmain(String[]args){//創(chuàng)建自定義線程對象1MyThreadDemomyThreadDemo=newMyThreadDemo("線程1");//啟動線程1myThreadDemo.start();//創(chuàng)建自定義線程對象2MyThreadDemomyThreadDemo02=newMyThreadDemo("線程2");//啟動線程2myThreadDemo02.start();//在main方法種執(zhí)行for循環(huán)for(inti=1;i<=10;i++){System.out.println("main線程:"+i);}}}在上述代碼中,`MyThreadDemo`類通過繼承`Thread`類實現(xiàn)了自定義線程的創(chuàng)建。`MyThreadDemo`類中重寫了`Thread`類的`run()`方法,該方法定義了線程被調(diào)度執(zhí)行時所需完成的任務(wù)。而在`ThreadDemoTest`類中,為了啟動這個自定義線程,必須調(diào)用其`start()`方法。線程創(chuàng)建與啟動-繼承Threrad類03小貼士:(1)如果自己手動調(diào)用`run()`方法,那么它就僅僅是一個普通的Java方法,并不會啟動多線程模式。(2)`run()`方法通常由JVM(Java虛擬機(jī))在特定時刻調(diào)用,而這個調(diào)用的時機(jī)以及執(zhí)行過程的控制,則完全由操作系統(tǒng)的CPU調(diào)度來決定。(3)若想啟動多線程,必須調(diào)用線程的`start()`方法。(4)一個線程對象只能被調(diào)用一次`start()`方法來啟動,若嘗試重復(fù)調(diào)用,則會拋出`IllegalThreadStateException`異常。(5)線程啟動流程讓如圖13-1所示線程創(chuàng)建與啟動-實現(xiàn)Runnable接口03Java具有單繼承的限制,當(dāng)無法直接繼承`Thread`類時,我們應(yīng)如何應(yīng)對呢?幸運的是,Java核心類庫中提供了`Runnable`接口,這一機(jī)制允許我們通過實現(xiàn)`Runnable`接口并重寫其`run()`方法來定義線程的執(zhí)行體。隨后,我們可以利用`Thread`類的對象作為代理,啟動并執(zhí)行我們自定義的`run()`方法,從而靈活地實現(xiàn)多線程編程。在實現(xiàn)`Runnable`接口具體流程如下:(1)定義Runnable接口的實現(xiàn)類,并重新實現(xiàn)該接口的run()方法,該run()方法的具體實現(xiàn)即為該線程的線程執(zhí)行體。(2)創(chuàng)建Runnable接口實現(xiàn)類的實例,并將此實例作為Thread類的target參數(shù)來構(gòu)造Thread對象,這個Thread對象即為真正的線程實體。(3)通過調(diào)用線程對象的start()方法來啟動線程。注意,不應(yīng)直接調(diào)用Runnable接口實現(xiàn)類的run方法,因為這樣做并不會啟動一個新的線程,而是像調(diào)用普通方法一樣在當(dāng)前線程中執(zhí)行run方法的內(nèi)容。線程創(chuàng)建與啟動-實現(xiàn)Runnable接口03【例13-2】實現(xiàn)Runnable接口,創(chuàng)建線程并啟動線程,代碼如下:packagecom.chapter13.create;publicclassMyTreadDemo02implementsRunnable{@Overridepublicvoidrun(){for(inti=0;i<20;i++){System.out.println(Thread.currentThread().getName()+""+i);}}}線程創(chuàng)建與啟動-實現(xiàn)Runnable接口03packagecom.chapter13.create;publicclassRunnableDemoTest{publicstaticvoidmain(String[]args){//創(chuàng)建自定義的對象,線程任務(wù)對象MyTreadDemo02myTreadDemo02=newMyTreadDemo02();//創(chuàng)建線程對象Threadthread=newThread(myTreadDemo02);//啟動線程對象thread.start();for(inti=0;i<10;i++){System.out.println("chapter13:"+i);}}}在上述代碼中,`MyTreadDemo02`類通過實現(xiàn)`Runnable`接口,并覆寫(重寫)其`run()`方法來定義線程的執(zhí)行任務(wù)。重要的是要明確,`MyTreadDemo02`并非直接代表線程對象本身,而是作為線程任務(wù)的一個載體或?qū)ο蟠嬖?。實際的線程對象需要在`RunnableDemoTest`類中通過適當(dāng)?shù)臉?gòu)造進(jìn)行創(chuàng)建,隨后通過調(diào)用`start()`方法來啟動這一線程,從而執(zhí)行`MyTreadDemo02`類中定義的`run()`方法內(nèi)的任務(wù)。實現(xiàn)`Runnable`接口避免了單繼承的局限性,多個線程可以共享同一個接口實現(xiàn)類的對象,非常適合多個相同線程來處理同一份資源。增加程序的健壯性,實現(xiàn)解耦操作,代碼可以被多個線程共享,代碼和線程獨立。線程創(chuàng)建與啟動-實現(xiàn)Runnable接口03小貼士:通過實現(xiàn)Runnable接口,該類便具備了多線程的特性。所有需要在分線程中執(zhí)行的代碼都應(yīng)當(dāng)被置于run方法之內(nèi)。在啟動多線程時,首先需要利用Thread類的構(gòu)造方法`Thread(Runnabletarget)`來創(chuàng)建一個Thread對象,并指定Runnable接口的實現(xiàn)類作為參數(shù)。隨后,通過調(diào)用該Thread對象的start()方法來啟動并執(zhí)行多線程代碼。實際上,無論采用何種方式實現(xiàn)多線程——無論是繼承Thread類還是實現(xiàn)Runnable接口,最終都是依賴于Thread對象的API來控制線程的。因此,熟悉Thread類的API是進(jìn)行多線程編程不可或缺的基礎(chǔ)。需要注意的是,Runnable對象在這里僅僅是作為Thread對象的target存在,而Runnable接口實現(xiàn)類中的run()方法則充當(dāng)了線程的執(zhí)行體。盡管實際的線程對象是Thread的實例,但正是這個Thread線程負(fù)責(zé)執(zhí)行其target所指定的run()方法中的代碼。線程創(chuàng)建與啟動-匿名內(nèi)部類創(chuàng)建啟動線程03使用匿名內(nèi)部類對象來實現(xiàn)線程的創(chuàng)建和啟動【例13-3】使用匿名內(nèi)部類,創(chuàng)建線程并啟動線程,代碼如下:packagecom.chapter13.create;publicclassAnonymousDemoTest{publicstaticvoidmain(String[]args){AnonymousDemoTestanonymousDemoTest=newAnonymousDemoTest();anonymousDemoTest.testThread();anonymousDemoTest.testRunn();}線程創(chuàng)建與啟動-匿名內(nèi)部類創(chuàng)建啟動線程03/***使用匿名內(nèi)部創(chuàng)建線程類,調(diào)用start方法,啟動線程*/publicvoidtestThread(){newThread(){publicvoidrun(){for(inti=0;i<10;i++){System.out.println(getName()+":正在執(zhí)行!"+i);}}}.start();}線程創(chuàng)建與啟動-匿名內(nèi)部類創(chuàng)建啟動線程03/***使用匿名內(nèi)部創(chuàng)建線程類,調(diào)用start方法,啟動線程*/publicvoidtestRunn(){newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<10;i++){System.out.println(Thread.currentThread().getName()+":"+i);}}}).start();}}在上述代碼中,在`testThread()`方法中,我們直接運用了`Thread`類來實例化一個線程對象,通過重寫其`run()`方法來定義線程的執(zhí)行任務(wù),隨后調(diào)用`start()`方法來啟動線程的執(zhí)行。而在`testRunn()`方法中,創(chuàng)建線程的方式則依賴于實現(xiàn)了`Runnable`接口的類,我們同樣需要重寫`run()`方法來指定線程的任務(wù)內(nèi)容,但啟動線程的方式稍有不同,這里是通過`Thread`類的構(gòu)造器傳入實現(xiàn)了`Runnable`接口的實例,并調(diào)用其`start()`方法來啟動線程。線程創(chuàng)建與啟動-繼承Thread類與實現(xiàn)Runnable接口的區(qū)別03(1)繼承Thread類:線程代碼存放Thread子類run方法中,直接定義的就是線程類,直接啟動線程。(2)實現(xiàn)Runnable接口:線程代碼存在接口的子類的run方法,定義的是線程任務(wù)類,需要通過Thread類創(chuàng)建線程類,再去啟動線程。線程的生命周期04Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經(jīng)歷如下一些狀態(tài):線程的生命周期有五種狀態(tài):新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多條線程之間切換,于是線程狀態(tài)會多次在運行、阻塞、就緒之間切換,如圖13-2所示線程的生命周期04接下來,我們將利用表13-1將線程狀態(tài)時進(jìn)行詳細(xì)闡述。線程狀態(tài)名稱線程狀態(tài)說明NEW(新建)線程剛被創(chuàng)建,但是并未啟動。還沒調(diào)用start方法。RUNNABLE(可運行)這里并未對就緒與運行狀態(tài)進(jìn)行明確區(qū)分。原因在于,針對Java對象而言,它們僅能被標(biāo)記為可運行狀態(tài),至于何時真正執(zhí)行,則并非由JVM直接控制,而是由操作系統(tǒng)(OS)負(fù)責(zé)調(diào)度。此過程極為短暫,故而在Java對象的狀態(tài)劃分上,難以對這兩種狀態(tài)作出明確界定。Teminated(被終止)表明此線程已經(jīng)結(jié)束生命周期,終止運行。BLOCKED(鎖阻塞)在API的說明中,這種狀態(tài)被定義為:線程正處于阻塞階段,等待獲取一個監(jiān)視器鎖(即鎖對象)。僅當(dāng)該線程成功獲取到鎖對象后,它才會獲得執(zhí)行的機(jī)會。線程的生命周期04線程狀態(tài)名稱線程狀態(tài)說明TIMED_WAITING(計時等待)在API中的介紹為:一個正在限時等待另一個線程執(zhí)行一個(喚醒)動作的線程處于這一狀態(tài)。TERMINATED(無線等待)在API中介紹為:一個正在無限期等待另一個線程執(zhí)行一個特別的(喚醒)動作的線程處于這一狀態(tài)。線程的生命周期04小貼士:當(dāng)從WAITING或TIMED_WAITING恢復(fù)到Runnable狀態(tài)時,如果發(fā)現(xiàn)當(dāng)前線程沒有得到監(jiān)視器鎖,那么會立刻轉(zhuǎn)入BLOCKED狀態(tài),如圖13-3所示多線程線程同步-資源線程安全問題05線程安全問題主要是指多個線程同時訪問和操作同一共享資源時,可能會導(dǎo)致數(shù)據(jù)的不一致性、程序的不可預(yù)測性以及錯誤的結(jié)果?!纠?3-4】火車站票務(wù)系統(tǒng)模擬:多窗口并行售票流程為了模擬火車站的票務(wù)銷售過程,我們將構(gòu)建一個場景,其中本次列車的座位總數(shù)為5個(即,火車票的最大銷售數(shù)量為5張)。在此框架下,我們將模擬車站的售票窗口功能,特別是多個售票窗口同時運行的情況,以確保高效且準(zhǔn)確的票務(wù)分配。重要提示:在模擬過程中,需嚴(yán)格遵循票務(wù)的唯一性和準(zhǔn)確性原則,避免任何形式的錯票或重票現(xiàn)象發(fā)生,代碼如下:多線程線程同步-資源線程安全問題05packagecom.chapter13.safe;publicclassWindowDemoextendsThread{privateintticket=5;@Overridepublicvoidrun(){while(ticket>0){System.out.println(getName()+"賣出一張票,票號:"+ticket);ticket--;}}}多線程線程同步-資源線程安全問題05packagecom.chapter13.safe;publicclassSafeTicketDemo{publicstaticvoidmain(String[]args){WindowDemow1=newWindowDemo();WindowDemow2=newWindowDemo();WindowDemow3=newWindowDemo();//給線程定義名稱w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//啟動線程w1.start();w2.start();w3.start();}}在上述代碼中,局部變量不可共享,若發(fā)現(xiàn)售出15張票的情況,需明確局部變量在每次方法調(diào)用時均保持獨立狀態(tài)。因此,每個線程的`run()`方法中的`ticket`變量也各自獨立,不存在數(shù)據(jù)共享問題。同樣地,不同實例對象的實例變量也各自獨立,互不共享數(shù)據(jù)。小貼士:使用靜態(tài)變量,實現(xiàn)共享數(shù)據(jù)。多線程線程同步-資源線程安全問題05【例13-5】在13-4案例基礎(chǔ)上進(jìn)行實現(xiàn)共享數(shù)據(jù),代碼如下:packagecom.chapter13.safe;publicclassWindowDemo02extendsThread{privatestaticintticket=5;publicvoidrun(){while(ticket>0){try{Thread.sleep(10);//加入這個,使得問題暴露的更明顯}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(getName()+"賣出一張票,票號:"+ticket);ticket--;}}}多線程線程同步-資源線程安全問題05packagecom.chapter13.safe;publicclassSafeTicketDemo03{publicstaticvoidmain(String[]args){//創(chuàng)建線程任務(wù)對象WindowDemo03tr=newWindowDemo03();//定義線程對象Threadt1=newThread(tr,"窗口一");Threadt2=newThread(tr,"窗口二");Threadt3=newThread(tr,"窗口三");//啟動線程t1.start();t2.start();t3.start();}}在上述代碼中,發(fā)現(xiàn)賣出近5張票,但是有重復(fù)票或負(fù)數(shù)票問題,依然存在線程的安全問題,在13.4.2章節(jié)中解決線程安全問題。多線程線程同步-資源線程安全問題05【例13-6】在13-5案例的基礎(chǔ)上,實現(xiàn)同一對象實例變量的共享使用,代碼如下:packagecom.chapter13.safe;publicclassWindowDemo03implementsRunnable{privateintticket=5;publicvoidrun(){while(ticket>0){try{Thread.sleep(10);//加入這個,使得問題暴露的更明顯}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"賣出一張票,票號:"+ticket);ticket--;}}}多線程線程同步-資源線程安全問題05packagecom.chapter13.safe;publicclassSafeTicketDemo03{publicstaticvoidmain(String[]args){//創(chuàng)建線程任務(wù)對象WindowDemo03tr=newWindowDemo03();//定義線程對象Threadt1=newThread(tr,"窗口一");Threadt2=newThread(tr,"窗口二");Threadt3=newThread(tr,"窗口三");//啟動線程t1.start();t2.start();t3.start();}}多線程線程同步-同步機(jī)制05要解決上述多線程并發(fā)訪問一個資源的安全性問題:也就是解決重復(fù)票與不存在票問題,Java中提供了同步機(jī)制(synchronized)來解決,如圖13-7所示。同步代碼塊:synchronized關(guān)鍵字可以用于某個區(qū)塊前面,表示只對這個區(qū)塊的資源實行互斥訪問。語法結(jié)構(gòu)如下:synchronized(同步鎖){
需要同步操作的代碼}同步方法:synchronized關(guān)鍵字直接修飾方法,表示同一時刻只有一個線程能進(jìn)入這個方法,其他線程在外面等著。多線程線程同步-同步機(jī)制05語法結(jié)構(gòu)如下:public
synchronized
void
method(){
可能會產(chǎn)生線程安全問題的代碼}同步鎖對象可以是任意類型,但是必須保證競爭“同一個共享資源”的多個線程必須使用同一個“同步鎖對象”。對于同步代碼塊來說,同步鎖對象是由程序員手動指定的(很多時候也是指定為this或類名.class),但是對于同步方法來說,同步鎖對象只能是默認(rèn)的:(1)靜態(tài)方法:當(dāng)前類的Class對象(類名.class)(2)非靜態(tài)方法:this1.同步方法(1)在靜態(tài)方法上添加同步鎖【例13-7】在基于13-6案例的情境下,實現(xiàn)數(shù)據(jù)共享機(jī)制以有效應(yīng)對并解決線程安全問題,代碼如下:packagecom.chapter13.safe;publicclassWindowDemo04extendsThread{privatestaticintticket=5;publicvoidrun(){//直接鎖這里,肯定不行,會導(dǎo)致,只有一個窗口賣票while(ticket>0){saleOneTicket();}}多線程線程同步-同步機(jī)制05publicsynchronizedstaticvoidsaleOneTicket(){//鎖對象是TicketSaleThread類的Class對象,而一個類的Class對象在內(nèi)存中肯定只有一個if(ticket>0){//不加條件,相當(dāng)于條件判斷沒有進(jìn)入鎖管控,線程安全問題就沒有解決System.out.println(Thread.currentThread().getName()+"賣出一張票,票號:"+ticket);ticket--;}}}多線程線程同步-同步機(jī)制05packagecom.chapter13.safe;publicclassSafeTicketDemo04{publicstaticvoidmain(String[]args){//創(chuàng)建線程對象WindowDemo04t1=newWindowDemo04();WindowDemo04t2=newWindowDemo04();WindowDemo04t3=newWindowDemo04();//設(shè)置線程名稱t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//啟動線程t1.start();t2.start();t3.start();}}在上述代碼中,實現(xiàn)了數(shù)據(jù)的共享機(jī)制,通過將`synchronized`同步鎖應(yīng)用于靜態(tài)方法之上,巧妙地解決了線程安全問題,確保了數(shù)據(jù)的一致性和完整性。多線程線程同步-同步機(jī)制05(2)在非靜態(tài)方法上添加同步鎖【例13-8】在基于13-6案例的情境下,實現(xiàn)數(shù)據(jù)共享機(jī)制以有效應(yīng)對并解決線程安全問題,代碼如下:packagecom.chapter13.safe;publicclassWindowDemo05implementsRunnable{privatestaticintticket=5;publicvoidrun(){//直接鎖這里,肯定不行,會導(dǎo)致,只有一個窗口賣票while(ticket>0){saleOneTicket();}}publicsynchronizedvoidsaleOneTicket(){//鎖對象是this,這里就是TicketSaleRunnable對象,因為上面3個線程使用同一個TicketSaleRunnable對象,所以可以if(ticket>0){//不加條件,相當(dāng)于條件判斷沒有進(jìn)入鎖管控,線程安全問題就沒有解決System.out.println(Thread.currentThread().getName()+"賣出一張票,票號:"+ticket);ticket--;}}}多線程線程同步-同步機(jī)制05packagecom.chapter13.safe;publicclassSafeTicketDemo05{publicstaticvoidmain(String[]args){//創(chuàng)建線程任務(wù)對象WindowDemo05tr=newWindowDemo05();//創(chuàng)建線程類Threadt1=newThread(tr,"窗口一");Threadt2=newThread(tr,"窗口二");Threadt3=newThread(tr,"窗口三");//啟動線程t1.start();t2.start();t3.start();}}在上述代碼中,實現(xiàn)了數(shù)據(jù)的共享機(jī)制,通過將`synchronized`同步鎖應(yīng)用于非靜態(tài)方法之上,巧妙地解決了線程安全問題,確保了數(shù)據(jù)的一致性和完整性。多線程線程同步-同步機(jī)制052.同步代碼塊將`synchronized`同步鎖應(yīng)用在代碼塊,解決了線程安全問題,確保了數(shù)據(jù)的一致性和完整性?!纠?3-9】在基于13-6案例的情境下,實現(xiàn)數(shù)據(jù)共享機(jī)制以有效應(yīng)對并解決線程安全問題,代碼如下:packagecom.chapter13.safe;publicclassWindowDemo06{privatestaticintticket=5;publicvoidsale(){//也可以直接給這個方法加鎖,鎖對象是this,這里就是Ticket對象if(ticket>0){System.out.println(Thread.currentThread().getName()+"賣出一張票,票號:"+ticket);ticket--;}else{thrownewRuntimeException("沒有票了");}}publicintgetTicket(){returnticket;}}多線程線程同步-同步機(jī)制05packagecom.chapter13.safe;publicclassSafeTicketDemo06{publicstaticvoidmain(String[]args){//2、創(chuàng)建資源對象WindowDemo06ticket=newWindowDemo06();//3、啟動多個線程操作資源類的對象Threadt1=newThread("窗口一"){publicvoidrun(){//不能給run()直接加鎖,因為t1,t2,t3的三個run方法分別屬于三個Thread類對象,//run方法是非靜態(tài)方法,那么鎖對象默認(rèn)選this,那么鎖對象根本不是同一個while(true){synchronized(ticket){ticket.sale();}}}};多線程線程同步-同步機(jī)制05Threadt2=newThread("窗口二"){publicvoidrun(){while(true){synchronized(ticket){ticket.sale();}}}};Threadt3=newThread(newRunnable(){publicvoidrun(){while(true){synchronized(ticket){ticket.sale();}}}},"窗口三");t1.start();t2.start();t3.start();}}在上述代碼中,實現(xiàn)了數(shù)據(jù)的共享機(jī)制,通過將`synchronized`同步鎖應(yīng)用于代碼塊之上,巧妙地解決了線程安全問題,確保了數(shù)據(jù)的一致性和完整性。多線程線程同步-同步機(jī)制05小貼士:當(dāng)窗口1的線程開始進(jìn)行操作時,窗口2和窗口3的線程則需在外部等待。直到窗口1的操作完成,窗口1、窗口2以及窗口3的線程才有機(jī)會進(jìn)入代碼執(zhí)行。換言之,在某一線程對共享資源進(jìn)行修改時,其他線程必須等待該修改完成并實現(xiàn)同步后,方可去競爭CPU資源,完成其各自的操作。這樣的機(jī)制確保了數(shù)據(jù)的同步性,從而解決了線程不安全的問題。為確保每個線程都能正確地執(zhí)行原子操作,Java引入了線程同步機(jī)制。特別值得注意的是,在任何時候,至多只有一個線程能持有同步鎖。獲得鎖的線程將進(jìn)入代碼塊執(zhí)行,而其他線程則處于阻塞(BLOCKED)狀態(tài),等待鎖的釋放。多線程線程同步-同步機(jī)制053.同步鎖lockjava.util.concurrent.locks.Lock機(jī)制提供了比synchronized代碼和synchronize方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>
Lock鎖也稱同步鎖,加鎖和釋放鎖方法化了。(1)publicvoidlock();加同步鎖(2)publicvoidunlock();釋放同步鎖;
【例13-10】在基于13-6案例的情境下,實現(xiàn)數(shù)據(jù)共享機(jī)制以有效應(yīng)對并解決線程安全問題,代碼如下:packagecom.chapter13.safe;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclassTicketDemoimplementsRunnable{staticintticket=6;//定義一個變量一共賣10張票staticLockl=newReentrantLock();@Overridepublicvoidrun(){while(true){method();}}多線程線程同步-同步機(jī)制05publicstaticvoidmethod(){l.lock();if(ticket>0){try{//Thread線程對象,調(diào)用sleep對象,只要睡覺,,就會失去cpu的執(zhí)行權(quán)力,睡醒之后繼續(xù)獲的cpu的權(quán)利去執(zhí)行,參數(shù)是毫秒數(shù)Thread.sleep(1000);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在買"+ticket--);}l.unlock();}}多線程線程同步-同步機(jī)制05packagecom.chapter13.safe;publicclassTicketDemoTest{publicstaticvoidmain(String[]args){//創(chuàng)建線程任務(wù):TicketDemoticketDemo=newTicketDemo();//創(chuàng)建三個窗口Threadthread1=newThread(ticketDemo);Threadthread2=newThread(ticketDemo);Threadthread3=newThread(ticketDemo);//同時賣票thread1.start();thread2.start();thread3.start();}}在上述代碼中,實現(xiàn)了數(shù)據(jù)的共享機(jī)制,通過將`lock`同步鎖應(yīng)用于代碼塊之上,巧妙地解決了線程安全問題,確保了數(shù)據(jù)的一致性和完整性。線程的通信-等待喚醒機(jī)制05"wait"狀態(tài)指的是線程進(jìn)入非活動狀態(tài),不再參與CPU調(diào)度,被加入至等待集合(waitset)中。在此狀態(tài)下,線程既不會消耗CPU資源,也不會參與鎖的競爭,其狀態(tài)標(biāo)記為WAITING或TIMED_WAITING。線程需要等待其他線程執(zhí)行特定的操作——“通知(notify)”或等待設(shè)定的時間到期,之后才會從等待集合中被釋放,并重新進(jìn)入調(diào)度隊列(readyqueue)中,準(zhǔn)備執(zhí)行。"notify"操作則是針對某一對象的等待集合中的一個線程進(jìn)行釋放,允許其繼續(xù)執(zhí)行。而"notifyAll"操作則是針對同一對象的等待集合中的所有線程進(jìn)行釋放,使它們都能繼續(xù)執(zhí)行。被喚醒的線程即便被通知,也可能無法立即恢復(fù)執(zhí)行,原因在于其原本中斷之處位于同步塊內(nèi),且當(dāng)前已不再持有所需的鎖。因此,它必須重新嘗試獲取鎖(這很可能涉及與其他線程的競爭),一旦成功,方能在調(diào)用wait方法后的斷點處繼續(xù)執(zhí)行。小貼士:如果能成功獲取鎖,線程將從WAITING狀態(tài)轉(zhuǎn)變?yōu)镽UNNABLE(可運行)狀態(tài);否則,線程將保持WAITING狀態(tài)并轉(zhuǎn)變?yōu)锽LOCKED(等待鎖)狀態(tài)。1.wait()方法和notify()方法的使用【例13-11】使用兩個線程交替打印數(shù)字1至100,線程1與線程2輪流輸出序列中的每個數(shù)字,代碼如下:線程的通信-等待喚醒機(jī)制05packagemunication;publicclassCommunicationDemoimplementsRunnable{inti=1;publicvoidrun(){while(true){synchronized(this){notify();if(i<=6){System.out.println(Thread.currentThread().getName()+":"+i++);}elsebreak;try{wait();}catch(InterruptedExceptione){e.printStackTrace();}}}}}線程的通信-等待喚醒機(jī)制05packagemunication;importernal.generic.DCMPG;publicclassCommunicationDemoTest{publicstaticvoidmain(String[]args){CommunicationDemocommunicationDemo=newCommunicationDemo();Threadthread=newThread(communicationDemo);Threadthread1=newThread(communicationDemo);thread.setName("線程1");thread1.setName("線程2");thread.start();thread1.start();}}在上述代碼中,使用`wait()`方法實現(xiàn)線程間的等待機(jī)制,確保線程間的有序執(zhí)行。首先線程1執(zhí)行打印操作,隨后線程2進(jìn)入等待狀態(tài);接著線程1暫停,線程2被`notify()`方法喚醒并執(zhí)行打印操作;最后線程2暫停,等待線程1的后續(xù)操作或再次被喚醒。這種機(jī)制有效地控制了線程的執(zhí)行順序。線程的通信-等待喚醒機(jī)制05小貼士:(1)wait方法和notify方法必須由同一個鎖對象來調(diào)用。原因如下:對應(yīng)的鎖對象能夠通過notify操作喚醒那些因調(diào)用該鎖對象的wait方法而進(jìn)入等待狀態(tài)的線程。(2)wait方法和notify方法是Object類的成員方法。這是因為:在Java中,鎖對象可以是任意對象,而所有Java對象都直接或間接地繼承了Object類,因此它們都可以使用這兩個方法。wait方法和notify方法必須在同步代碼塊或同步方法中調(diào)用。這是因為:這兩個方法的調(diào)用必須依賴于鎖對象,如果不在同步代碼塊或同步方法中,就沒有明確的鎖對象來調(diào)用它們,這時會拋出java.lang.IllegalMonitorStateException異常。線程的通信-等待喚醒機(jī)制052.生產(chǎn)者與消費者生產(chǎn)者與消費者問題實質(zhì)上蘊(yùn)含了兩大核心議題:(1)線程安全挑戰(zhàn):鑒于生產(chǎn)者與消費者共享數(shù)據(jù)緩沖區(qū)的特性,這自然引發(fā)了安全性的考量。不過,這一問題可通過實施同步機(jī)制來有效化解。(2)線程協(xié)作難題:①為解決此難題,關(guān)鍵在于確保生產(chǎn)者線程在緩沖區(qū)滿載時能夠自動進(jìn)入等待(wait)狀態(tài),即進(jìn)入阻塞模式,直至消費者線程消耗了緩沖區(qū)中的數(shù)據(jù)并發(fā)出通知(notify),使等待的線程恢復(fù)就緒狀態(tài),繼續(xù)向緩沖區(qū)添加數(shù)據(jù)。②同理,消費者線程在緩沖區(qū)為空時也應(yīng)進(jìn)入等待狀態(tài),暫停執(zhí)行直至生產(chǎn)者向緩沖區(qū)添加新數(shù)據(jù)并發(fā)出通知,隨后消費者線程方可恢復(fù)并繼續(xù)處理數(shù)據(jù)。這種精巧的通信機(jī)制正是解決此類協(xié)作問題的關(guān)鍵所在。線程的通信-等待喚醒機(jī)制05【例13-12】生產(chǎn)者(Producer)將產(chǎn)品交付給店員(Clerk),消費者(Customer)則從店員處領(lǐng)取所需產(chǎn)品。店員一次僅能管理固定數(shù)量的產(chǎn)品(例如:5件)。若生產(chǎn)者欲生產(chǎn)超出此數(shù)量的產(chǎn)品,店員會指示其暫停生產(chǎn),待店內(nèi)空間充足時再行通知恢復(fù)生產(chǎn)。相反,若店內(nèi)產(chǎn)品售罄,店員會告知消費者稍作等待,一旦有新產(chǎn)品入庫便立即通知其前來領(lǐng)取,代碼如下:packagemunication;publicclassClerk{Stringpi;Stringxian;booleanflag=false;}packagemunication;publicclassProducerimplementsRunnable{privateClerkbz;publicProducer(Clerkbz){this.bz=bz;}線程的通信-等待喚醒機(jī)制05@Overridepublicvoidrun(){while(true){synchronized(bz){//對包子鋪進(jìn)行判斷if(bz.flag==true){//調(diào)用wait方法,進(jìn)行等待try{bz.wait();}catch(InterruptedExceptione){e.printStackTrace();}}/*包子鋪線程開始生產(chǎn)包子*/bz.pi="薄皮";bz.xian="大餡";//生產(chǎn)的是xx皮xx餡System.out.println("包子鋪正在生產(chǎn)包子,生產(chǎn)的是:"+bz.pi+bz.xian+"請稍微等待幾秒");線程的通信-等待喚醒機(jī)制05//生產(chǎn)包子花費了3秒try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}//生產(chǎn)包子完畢之后,修改包子的狀態(tài)true,bz.flag=true;bz.notify();System.out.println("包子鋪已經(jīng)生產(chǎn)好了美味的"+bz.pi+bz.xian+"的包子");}}}}線程的通信-等待喚醒機(jī)制05//生產(chǎn)包子花費了3秒try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}//生產(chǎn)包子完畢之后,修改包子的狀態(tài)true,bz.flag=true;bz.notify();System.out.println("包子鋪已經(jīng)生產(chǎn)好了美味的"+bz.pi+bz.xian+"的包子");}}}}線程的通信-等待喚醒機(jī)制05packagemunication;publicclassConsumerimplementsRunnable{privateClerkbz;publicConsumer(Clerkbz){this.bz=bz;}@Overridepublicvoidrun(){while(true){synchronized(bz){//判斷是否有包子if(bz.flag==false){try{bz.wait();}catch(InterruptedExceptione){e.printStackTrace();}}線程的通信-等待喚醒機(jī)制05//吃貨線程開始吃包子System.out.println("吃貨正在吃包子,吃的是"+bz.pi+bz.xian+"包子");//吃完包子修改包子的狀態(tài)bz.flag=false;//吃貨線程喚醒包子鋪線程,做包子bz.notify();//喚醒包子上等待的包子鋪線程System.out.println("吃貨已經(jīng)吃完了包子,包子鋪趕緊生產(chǎn)包子把");System.out.println("============================");}}}}線程的通信-等待喚醒機(jī)制05packagemunication;publicclassConsumerProducerTest{publicstaticvoidmain(String[]args){Clerkbz=newClerk();ProducerbaoziPu=newProducer(bz);Consumerch=newConsumer(bz);newThread(baoziPu).start();newThread(ch).start();}}在上述代碼中,Clerk類代表店員,維護(hù)一個存儲產(chǎn)品的隊列和信號量來控制生產(chǎn)者和消費者的操作。Producer類代表生產(chǎn)者,不斷生產(chǎn)產(chǎn)品并交給店員。Consumer類代表消費者,從店員處領(lǐng)取產(chǎn)品。通過信號量的控制,確保了店員的庫存容量不超過固定數(shù)量,并且在庫存為空時消費者會等待,在庫存滿時生產(chǎn)者會等待。線程的通信-等待喚醒機(jī)制054.鎖的操作(1)釋放鎖的操作①在任何線程嘗試進(jìn)入一個同步代碼塊或同步方法之前,它必須首先獲得相應(yīng)的同步監(jiān)視器的鎖定。當(dāng)前線程完成同步方法或同步代碼塊的執(zhí)行。②當(dāng)前線程在同步代碼塊或同步方法中遇到break或return語句,導(dǎo)致代碼塊或方法的執(zhí)行被終止。③當(dāng)前線程在同步代碼塊或同步方法中遇到未處理的Error或Exception,導(dǎo)致線程異常終止。④當(dāng)前線程在同步代碼塊或同步方法中調(diào)用了鎖對象的wait()方法,這將導(dǎo)致當(dāng)前線程被掛起,并同時釋放鎖。請注意,以上情況均不會改變原文的基本意思和核心觀點。(2)不會釋放鎖的操作①線程執(zhí)行同步代碼塊或同步方法時,程序調(diào)用Thread.sleep()、Thread.yield()方法暫停當(dāng)前線程的執(zhí)行。②線程執(zhí)行同步代碼塊時,其他線程調(diào)用了該線程的suspend()方法將該該線程掛起,該線程不會釋放鎖(同步監(jiān)視器)。③應(yīng)盡量避免使用suspend()和resume()這樣的過時來控制線程。線程池-為什么使用線程池06當(dāng)系統(tǒng)中存在大量并發(fā)線程,且每個線程僅執(zhí)行短暫任務(wù)后即結(jié)束,頻繁地創(chuàng)建和銷毀線程會顯著降低系統(tǒng)效率。這是因為線程的創(chuàng)建和銷毀過程本身消耗時間。那么,是否存在一種機(jī)制,使得線程能夠被重復(fù)利用呢?即線程在完成一個任務(wù)后,并不立即銷毀,而是繼續(xù)執(zhí)行其他任務(wù)。解決方案是預(yù)先創(chuàng)建一組線程,并將它們存儲在所謂的線程池中。當(dāng)需要執(zhí)行任務(wù)時,可以直接從線程池中獲取線程,任務(wù)完成后,線程被歸還到池中,從而避免了頻繁的創(chuàng)建和銷毀過程,實現(xiàn)了線程的復(fù)用。這與我們生活中的公共交通工具模式類似,如圖13-8所示線程池-線程池的優(yōu)點06(1)提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)(2)降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)(3)便于線程管理①corePoolSize:核心池的大小②maximumPoolSize:最大線程數(shù)③keepAliveTime:線程沒有任務(wù)時最多保持多長時間后會終止④…線程池-線程池相關(guān)API06JDK5.0之前,我們必須手動自定義線程池。從JDK5.0開始,Java內(nèi)置線程池相關(guān)的API。在java.util.concurrent包下提供了線程池相關(guān)API:ExecutorService和Executors。(1)ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor①voidexecute(Runnablecommand):執(zhí)行任務(wù)/命令,沒有返回值,一般用來執(zhí)行Runnable②<T>Future<T>submit(Callable<T>task):執(zhí)行任務(wù),有返回值,一般又來執(zhí)行Callable③voidshutdown():
溫馨提示
- 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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 房屋合同范本簡約模板
- 購房合同范本賣方有利
- 繳納人防費合同范本
- 舞蹈服裝合同范本
- 直播團(tuán)隊合作合同范本
- 小區(qū)車位轉(zhuǎn)讓合同范本
- 閃銀借款合同范本
- 設(shè)備清理保潔合同范本
- 陽邏租房合同范本
- 安全檢查合同
- 孕期健康方式課件
- 膏藥生產(chǎn)現(xiàn)場管理制度
- 智人遷徙路徑重構(gòu)-洞察及研究
- 關(guān)于醫(yī)院“十五五”發(fā)展規(guī)劃(2026-2030)
- T/SHPTA 082-2024光伏組件封裝用共擠EPE膠膜
- T/CCSAS 023-2022危險化學(xué)品企業(yè)緊急切斷閥設(shè)置和使用規(guī)范
- 農(nóng)莊魚塘出租合同范本
- 城管執(zhí)法理論知識培訓(xùn)課件講義
- 河南鄭州航空港發(fā)展投資集團(tuán)有限公司招聘筆試真題2024
- 高中語文課程標(biāo)準(zhǔn)深度解讀
- 制冷系統(tǒng)事故應(yīng)急預(yù)案
評論
0/150
提交評論