1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程【推薦-】_第1頁(yè)
1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程【推薦-】_第2頁(yè)
1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程【推薦-】_第3頁(yè)
1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程【推薦-】_第4頁(yè)
1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程【推薦-】_第5頁(yè)
已閱讀5頁(yè),還剩50頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

1、1.理解進(jìn)程和線程的概念,學(xué)習(xí)java中線程的使用 ;2.掌握線程的狀態(tài)和生命周期、線程的調(diào)度和控制方法;3.理解多線程的互斥和同步的實(shí)現(xiàn)原理,以及多線程的應(yīng)用,能夠熟練編寫關(guān)于線程、線程間的同步與通信的小程序 第9章 多線程 教學(xué)目的要求9.1 多線程的概念進(jìn)程和線程進(jìn)程就是在計(jì)算機(jī)中正在執(zhí)行的程序即處于活動(dòng)狀態(tài)的程序,每一個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間和一組系統(tǒng)資源,比方在Windows、Linux等操作系統(tǒng)中可以同時(shí)執(zhí)行多個(gè)程序,這里的每一個(gè)運(yùn)行的程序都是一個(gè)進(jìn)程,這些程序使用的內(nèi)存空間和系統(tǒng)資源都是獨(dú)立的,并且每個(gè)程序也是為了各自的任務(wù)而運(yùn)行,互不干擾。進(jìn)程概念的引入是操作系統(tǒng)開(kāi)展史

2、上的一個(gè)里程碑,正是進(jìn)程技術(shù)的引入才使得計(jì)算機(jī)操作系統(tǒng)同時(shí)處理多個(gè)任務(wù)成為可能,這也促使了像Windows一樣的多任務(wù)操作系統(tǒng)的出現(xiàn),使計(jì)算機(jī)的運(yùn)行效率在很大程度上得到了提升。在進(jìn)程的根底上,線程概念后來(lái)又被提出,它使得在一個(gè)進(jìn)程中同時(shí)處理多個(gè)任務(wù)成為可能。線程和進(jìn)程有很多相似的特征,線程可以被看作是進(jìn)程的進(jìn)一步細(xì)分,也就是把進(jìn)程完成的任務(wù)劃分成一個(gè)個(gè)更小的子任務(wù),每一個(gè)子任務(wù)就是一個(gè)線程,然后用細(xì)分的這些線程分別去執(zhí)行。線程是基于進(jìn)程的一個(gè)根本運(yùn)行單位,線程同樣包括一個(gè)內(nèi)存入口點(diǎn)地址、一個(gè)出口點(diǎn)地址以及能夠順序執(zhí)行的代碼序列。但是線程與進(jìn)程的重要區(qū)別在于線程不能夠單獨(dú)執(zhí)行,它必須運(yùn)行在處于活

3、動(dòng)狀態(tài)的進(jìn)程中,線程本身的數(shù)據(jù)通常只有微處理器的存放器數(shù)據(jù)以及一個(gè)供程序執(zhí)行時(shí)使用的堆棧,因此可以定義線程是程序內(nèi)部的順序代碼流,也就是說(shuō)線程是在進(jìn)程作用域內(nèi)活動(dòng)的一系列指令流,所以線程也被稱為輕型進(jìn)程(Light Weight Process,LWP)。多線程多線程允許在程序中“并行執(zhí)行多個(gè)指令流,每個(gè)指令流被稱作一個(gè)線程,彼此間的執(zhí)行互相獨(dú)立。多線程需要操作系統(tǒng)的支持,WIN32平臺(tái)支持多線程程序,允許程序中存在多個(gè)線程。在單CPU計(jì)算機(jī)系統(tǒng)中,系統(tǒng)把CPU的時(shí)間片按照調(diào)度算法分配給各個(gè)線程,因此各線程實(shí)際上是分時(shí)執(zhí)行的,而在多CPU的計(jì)算機(jī)系統(tǒng)中,同一個(gè)程序的不同線程可以分配到不同的CP

4、U上去執(zhí)行。多個(gè)線程的執(zhí)行是并發(fā)的,也就是在邏輯上“同時(shí),而不是物理上的“同時(shí)。如果系統(tǒng)只有一個(gè)CPU,那么真正的“同時(shí)是不可能的,但是由于CPU的速度非常快,用戶感覺(jué)不到其中的區(qū)別。 (a)單線程程序 (b)多線程程序圖9.1 單線程與多線程的比照J(rèn)ava中的多線程機(jī)制線程需要計(jì)算機(jī)系統(tǒng)的支持,并不是所有類型的計(jì)算機(jī)都支持多線程應(yīng)用程序。但是由于Java引入了虛擬處理器技術(shù),所以Java語(yǔ)言將線程支持與語(yǔ)言運(yùn)行環(huán)境結(jié)合在一起,不管在任何系統(tǒng)下,Java語(yǔ)言都提供了多任務(wù)并發(fā)執(zhí)行的能力,如圖9.2所示。這就好比一個(gè)人在處理家務(wù)的過(guò)程中,將米放在電飯鍋里后再把衣服放到洗衣機(jī)中自動(dòng)洗滌,然后開(kāi)始做

5、菜,等菜做好了,飯也熟了,同時(shí)衣服也洗好了。只要合理安排各個(gè)線程的運(yùn)行,就可以極大地提高程序的運(yùn)行效率。 對(duì)多線程的綜合支持是Java語(yǔ)言的一個(gè)重要特色,在Java中,內(nèi)置了Thread類來(lái)實(shí)現(xiàn)多線程,當(dāng)程序引用了java.lang.Thread類,也就引入了一個(gè)Java執(zhí)行環(huán)境。由圖9.2可知,一個(gè)線程是由三局部組成的:虛擬處理機(jī)CPU,封裝在java.lang.Thread類中,它控制著整個(gè)線程的運(yùn)行,提供對(duì)多線程的支持;執(zhí)行的程序代碼,傳遞給Thread類,由Thread類控制順序執(zhí)行;程序所處理的數(shù)據(jù),傳遞 給Thread類,是在代碼執(zhí) 行過(guò)程中所要處理的數(shù)據(jù)。虛擬CPUCode Da

6、ta圖9.2 線程的組成在Java編程中,虛擬處理機(jī)CPU被封裝在Thread線程類的實(shí)例之中。這樣一來(lái),有多少個(gè)需要完成的子任務(wù)線程就有多少個(gè)虛擬CPU這樣的“虛擬計(jì)算機(jī)在同時(shí)運(yùn)行,把一個(gè)較大的任務(wù)分割成許多較小的子任務(wù)分別地、“同時(shí)地去完成,這就是Java多線程機(jī)制。Java的線程是通過(guò)java.lang包中定義的類Thread來(lái)實(shí)現(xiàn)的。當(dāng)生成一個(gè)Thread類的對(duì)象之后,就產(chǎn)生了一個(gè)線程,通過(guò)該對(duì)象實(shí)例,可以啟動(dòng)線程、終止線程、或者暫時(shí)掛起線程等。由于Java在語(yǔ)言級(jí)提供了對(duì)線程的支持,所以在Java語(yǔ)言中使用多線程要遠(yuǎn)比在其它語(yǔ)言中使用線程簡(jiǎn)單得多。9.2 線程類及其線程創(chuàng)立線程類 通

7、常在Java程序設(shè)計(jì)中,任何機(jī)制都是基于類的,當(dāng)然線程也不例外,所以要學(xué)會(huì)Java中的多線程編程,就必需了解實(shí)現(xiàn)線程的Thread類。線程對(duì)象實(shí)例表示Java程序中的真正的線程,通過(guò)它可以啟動(dòng)線程、終止線程、掛起線程等操作,Thread類是負(fù)責(zé)向其它類提供線程支持的最主要的類,Thread類在包java.lang中定義,它的構(gòu)造方法為: public ThreadThreadGroup group,Runnable target,String name; 其中,group指明該線程所屬的線程組關(guān)于線程組將在后面詳細(xì)講解;target是指實(shí)際執(zhí)行線程體的目標(biāo)對(duì)象,它必須實(shí)現(xiàn)接口Runnable;

8、name為線程名,Java中的每個(gè)線程都有自己的名稱,Java提供了不同Thread類構(gòu)造器,允許給線程指定名稱,如果name為null時(shí),那么Java將自動(dòng)為其分配一個(gè)唯一的名稱。 在線程構(gòu)造方法中,每個(gè)參數(shù)都可以為空,當(dāng)上述構(gòu)造方法的某個(gè)參數(shù)為null時(shí),就可以得到下面的幾個(gè)構(gòu)造方法:public Thread;public ThreadString name;public ThreadRunnable target;public ThreadRunnable target,String name;public ThreadThreadGroup group,String name;pub

9、lic ThreadThreadGroup group,Runnable target; Thread類中也有許多有用的方法,在這里只介紹局部常用的方法:1. public static void yield;引起當(dāng)前執(zhí)行線程暫停,允許其他線程執(zhí)行。2. public static void sleep(long millis);使當(dāng)前執(zhí)行的線程睡眠指定的時(shí)間。參數(shù)millis是線程睡眠的毫秒數(shù)。如果這個(gè)線程已經(jīng)被別的線程中斷,就會(huì)產(chǎn)生InterruptedException異常。3. public void start;使線程由新建狀態(tài)變成可運(yùn)行狀態(tài)。4. public void run;如

10、果線程實(shí)例是使用實(shí)現(xiàn)了Runnable接口的類的實(shí)例創(chuàng)立的,就調(diào)用這個(gè)類的實(shí)例的run( )方法,否那么什么都不做并返回。5. public final void stop;停止殺死當(dāng)前線程。6. public final void wait;返回當(dāng)前線程所在的線程組中的線程的數(shù)目7. public final boolean isAlive;測(cè)試線程是否處于活動(dòng)狀態(tài),即已啟動(dòng),但還沒(méi)有終止。8. public final void setPriority(int new);改變線程的優(yōu)先級(jí)。9. public static Thread currenthread;返回當(dāng)前執(zhí)行線程對(duì)象的引用。

11、10. public final void notify;喚醒一個(gè)等待中的線程。11. public final void notifyAll;喚醒所有處于等待中的線程。線程的創(chuàng)立在Java中,每個(gè)程序至少自動(dòng)擁有一個(gè)線程,稱為主線程,當(dāng)程序加載到內(nèi)存時(shí),啟動(dòng)主線程,如果需要使用其它線程,那么可以采用以下兩種方式創(chuàng)立新的線程:一種是擴(kuò)展java.lang.Thread類,用它覆蓋Thread類的run( )方法;另一種是編寫一個(gè)類,使之實(shí)現(xiàn)java.lang.Runnable接口,然后在Thread構(gòu)造函數(shù)中使用它。第一種方式只能在類沒(méi)有擴(kuò)展其它任何類的情況下才能使用,因?yàn)镴ava不允許多重繼

12、承。因此,如果一個(gè)類要繼承其它的類,最好選用第二種方法,這樣會(huì)有更大的靈活性。下面分別介紹兩種創(chuàng)立線程的方式。1.擴(kuò)展Thread類 類Thread位于java.lang包中,由于java.lang包自動(dòng)被導(dǎo)入每個(gè)Java程序中,所以可以直接使用類Thread而無(wú)需在Java程序開(kāi)始處編寫import語(yǔ)句,也這說(shuō)明了Java對(duì)線程支持的徹底性。通過(guò)這個(gè)類中的方法,可以啟動(dòng)、終止、中斷線程以及查詢、設(shè)置線程的當(dāng)前狀態(tài)。 使用擴(kuò)展Thread類的方式創(chuàng)立并執(zhí)行一個(gè)線程,需要執(zhí)行下面4個(gè)步驟:擴(kuò)展java.lang.Thread的類;用希望的執(zhí)行代碼來(lái)實(shí)現(xiàn)run方法;通過(guò)new關(guān)鍵字實(shí)例化該類的一個(gè)

13、新對(duì)象即一個(gè)線程;通過(guò)調(diào)用start方法啟動(dòng)線程。例如: public class yourThread extends Thread public run/需要以線程方式運(yùn)行的代碼,也就是所希望的執(zhí)行代碼 上面的代碼完成了擴(kuò)展Thread類和重寫了run( )方法,為了在程序中使用線程,還需要?jiǎng)?chuàng)立一個(gè)對(duì)象并調(diào)用run( )方法,如: yourThread tt=new yourThread; tt.start; 在這里,start方法將自動(dòng)調(diào)用在線程體內(nèi)重寫的run方法來(lái)執(zhí)行線程的具體處理,當(dāng)run方法執(zhí)行完畢時(shí),線程將自動(dòng)結(jié)束。 下面我們將通過(guò)例9.1學(xué)習(xí)如何使用擴(kuò)展Thread類的方法來(lái)

14、實(shí)現(xiàn)線程。例9.1 擴(kuò)展Thread類創(chuàng)立線程。本例通過(guò)擴(kuò)展Thread類來(lái)創(chuàng)立線程的方法,類Li9_01聲明為Thread的子類,具體創(chuàng)立方法可以用下面的代碼來(lái)描述。public class Li9_01 extends Thread int count = 1, number; public Li9_01(int num) /通過(guò)形參num接收參數(shù)傳遞 number = num; System.out.println(創(chuàng)立線程 + number); public void run( ) /重新構(gòu)造run( )方法 while(true) System.out.println(線程 + nu

15、mber + :計(jì)數(shù) + count); if (+count = 6) return; /run public static void main(String args) for(int i = 0; i 10; i+) Li9_01 tt = new Li9_01(i+1);/構(gòu)造屬Li9_01類的線程tt,并向線程傳遞參數(shù) tt.start( );/通過(guò)調(diào)用start( )方法 /main( ) 在例9.1中構(gòu)造了一個(gè)稱為tt的Li9_01類的線程,并在構(gòu)造后通過(guò)循環(huán)調(diào)用了start()方法屢次啟動(dòng)這個(gè)線程,這可以在運(yùn)行后的結(jié)果中看出。需要注意的是,在Java中,Thread對(duì)象通過(guò)st

16、art()方法啟動(dòng)線程而不能直接調(diào)用run方法,在調(diào)用start()方法后首先進(jìn)行一些初始化,然后再調(diào)用run方法啟動(dòng)線程。由于main()線程其實(shí)main()本身就可以看作是一個(gè)線程和tt線程的調(diào)度情況是由操作系統(tǒng)動(dòng)態(tài)決定的,因此編譯并運(yùn)行這個(gè)程序,每次運(yùn)行的結(jié)果不一定相同。這種方法簡(jiǎn)單明了,符合大家的習(xí)慣。但是它的缺點(diǎn)是:如果類已經(jīng)從一個(gè)類繼承,那么無(wú)法再繼承Thread 類,這時(shí)如果又不想建立一個(gè)新的類,應(yīng)該怎么辦呢?在這里不妨來(lái)探索一種新的方法:不創(chuàng)立 Thread 類的子類,而是直接使用它,那么只能將方法作為參數(shù)傳遞給Thread類的實(shí)例,有點(diǎn)類似回調(diào)函數(shù)。但是 Java 沒(méi)有指針,

17、只能傳遞一個(gè)包含這個(gè)方法的類的實(shí)例。那么如何限制這個(gè)類必須包含這一方法呢?當(dāng)然是使用接口!Java 提供了接口 java.lang. Runnable 來(lái)支持這種方法。2.實(shí)現(xiàn)Runnable接口 利用Runnable接口創(chuàng)立和運(yùn)行線程的編程步驟為: 第1步:定義一個(gè)Runnable接口的實(shí)現(xiàn)類,如MyThread,其內(nèi)必須實(shí)現(xiàn)Runnable接口所聲明的run( )方法。 定義Runnable接口的方法如下:public class yourThread implements Runnable public void run. /需要以線程方式運(yùn)行的代碼第2步:創(chuàng)立一個(gè)Thread類的對(duì)象,

18、即創(chuàng)立一個(gè)新線程,并用Runnable接口或者Thread類的引用變量指向它,調(diào)用Thread類帶Runnable引用作為形參的構(gòu)造方法,把Runnable接口實(shí)現(xiàn)類對(duì)象傳遞給Thread類的對(duì)象即傳遞給新線程,為新線程提供程序代碼和數(shù)據(jù)。如: yourThread yourt = new yourThread; Thread tt = new Thread(yourt) ;第3步:用線程對(duì)象調(diào)用start方法啟動(dòng)線程。如: tt.start; 定義一個(gè)實(shí)現(xiàn)了Runnable接口的類,將該類的對(duì)象作為Thread類的構(gòu)造方法的參數(shù),生成的Thread對(duì)象即為想要?jiǎng)?chuàng)立的線程,這樣的線程同樣通過(guò)s

19、tart( )方法啟動(dòng)。 例9.2將例9.1改為通過(guò)接口Runnable創(chuàng)立線程的實(shí)例。具體創(chuàng)立方法可以用下面的代碼來(lái)描述。/通過(guò)Runnable接口創(chuàng)立線程。Li9_02.javapublic class Li9_02 implements Runnable /定義Runnable接口int count= 1, number ; public Li9_02(int num) number = num ; System.out.println(創(chuàng)立線程 + number); public void run() /重新構(gòu)造run方法while(true) System.out.println(線

20、程 + number + :計(jì)數(shù) + count); if(+count= 6) return; /runpublic static void main(String args) for(int i = 0; i 10; i+) Li9_02 yourt = new Li9_02(i+1); Thread tt = new Thread(yourt); tt.start(); /啟動(dòng)線程 /main 從以上創(chuàng)立線程的實(shí)例可以看出,構(gòu)造線程體的兩種方法各自的優(yōu)缺點(diǎn)分析如下:1.使用Runnable接口可以將CPU、代碼和數(shù)據(jù)分開(kāi),形成清晰的模型;可以從其他類繼承,當(dāng)一個(gè)線程已繼承了另一個(gè)類時(shí),就

21、只能用實(shí)現(xiàn)Runnable接口的方法來(lái)創(chuàng)立線程;便于保持程序風(fēng)格的一致性。2.擴(kuò)展Thread類不能再?gòu)钠渌惱^承,適用于單繼承線程情況;編寫簡(jiǎn)單,可以直接操作線程; 由以上分析可知,兩種方法各有利弊,讀者應(yīng)該根據(jù)實(shí)際情況靈活運(yùn)用。線程的狀態(tài)與控制在這里需要明確的是:無(wú)論采用擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口的方法來(lái)實(shí)現(xiàn)應(yīng)用程序的多線程能力,都需要在該類中定義用于完成實(shí)際功能的run()方法,這個(gè)run()方法稱為線程體Thread body。按照線程體在計(jì)算機(jī)系統(tǒng)內(nèi)存中的狀態(tài),可以將線程從產(chǎn)生到滅亡分為新建、就緒、運(yùn)行、掛起、死亡等5種狀態(tài)。圖9.3描述了線程的生命周期的幾種狀態(tài)

22、和狀態(tài)之間的轉(zhuǎn)換。圖9.3 線程的生命周期新建狀態(tài):線程在已經(jīng)利用new關(guān)鍵字創(chuàng)立但是還未執(zhí)行的這段時(shí)間里,處于一種特殊的新建狀態(tài)中,此時(shí),線程對(duì)象已經(jīng)被分配了內(nèi)存空間,私有數(shù)據(jù)已經(jīng)被初始化,但是該線程尚未被調(diào)度。此時(shí)的線程可以被調(diào)度,變成可運(yùn)行狀態(tài),也可以被殺死,變成死亡狀態(tài)。就緒狀態(tài):在處于創(chuàng)立狀態(tài)的線程中調(diào)用start方法將線程的狀態(tài)轉(zhuǎn)換為就緒狀態(tài)。這時(shí),線程已經(jīng)得到除CPU時(shí)間之外的其它系統(tǒng)資源,只等JVMJava虛擬機(jī)的線程調(diào)度器按照線程的優(yōu)先級(jí)對(duì)該線程進(jìn)行調(diào)度,從而使該線程擁有能夠獲得CPU時(shí)間片的時(shí)機(jī)。 運(yùn)行狀態(tài):運(yùn)行狀態(tài)說(shuō)明線程正在運(yùn)行,該線程已經(jīng)擁有了對(duì)CPU的控制權(quán)。這個(gè)線

23、程一直運(yùn)行到運(yùn)行完畢,除非該線程主動(dòng)放棄CPU的控制權(quán)或者CPU的控制權(quán)被優(yōu)先級(jí)更高的線程搶占。處在運(yùn)行狀態(tài)的線程在以下情況下將讓出CPU的控制權(quán):線程運(yùn)行完畢;有比當(dāng)前進(jìn)程優(yōu)先級(jí)更高的線程進(jìn)入可運(yùn)行狀態(tài);線程主動(dòng)睡眠一段時(shí)間;線程在等待某一資源。掛起狀態(tài):如果一個(gè)線程處于掛起狀態(tài),那么暫時(shí)這個(gè)線程無(wú)法進(jìn)入就緒隊(duì)列。處于掛起狀態(tài)的線程通常需要由某些事件才能喚醒,至于由什么事件喚醒該線程,那么取決于其掛起的原因。處于睡眠狀態(tài)的線程必須被掛起一段固定的時(shí)間,當(dāng)睡眠時(shí)間結(jié)束時(shí)就變成可運(yùn)行狀態(tài);因等待資源或消息而被掛起的線程那么需要由一個(gè)外來(lái)事件喚醒。死亡狀態(tài):正常情況下run返回使得線程死亡。調(diào)用s

24、top或destroy亦有同樣效果,但是不被推薦,因?yàn)榍罢邥?huì)產(chǎn)生異常,后者是強(qiáng)制終止,不會(huì)釋放內(nèi)存。到目前為止,我們僅運(yùn)行了自動(dòng)終止的線程,它們執(zhí)行了一項(xiàng)任務(wù),然后終止。但是如何控制Java線程的終止、如何停止一個(gè)線程?這就需要使用一些方法來(lái)控制線程。表9-1列出了控制線程執(zhí)行的方法,可以使用表9-1中的方法創(chuàng)立線程并控制線程的執(zhí)行。線程調(diào)用的意義在于JVM應(yīng)對(duì)運(yùn)行的多個(gè)線程進(jìn)行系統(tǒng)級(jí)的協(xié)調(diào),以防止多個(gè)線程爭(zhēng)用有限資源而導(dǎo)致應(yīng)用系統(tǒng)死機(jī)或者崩潰。Java定義了線程的優(yōu)先級(jí)策略。Java將線程的優(yōu)先級(jí)分為10個(gè)等級(jí),分別用110之間的數(shù)字表示,數(shù)字越大說(shuō)明線程的級(jí)別越高。相應(yīng)地,在Thread類

25、中定義了表示線程最低、最高和普通優(yōu)先級(jí)的成員變量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的優(yōu)先級(jí)等級(jí)分別為1、10和5。當(dāng)一個(gè)線程對(duì)象被創(chuàng)立時(shí),其默認(rèn)的線程優(yōu)先級(jí)是5。為了控制線程的運(yùn)行策略,Java定義了線程調(diào)度器來(lái)監(jiān)控系統(tǒng)中處于就緒狀態(tài)的所有線程。線程調(diào)度器按照線程的優(yōu)先級(jí)決定哪個(gè)線程投入處理器運(yùn)行。在多個(gè)線程處于就緒狀態(tài)的條件下,具有高優(yōu)先級(jí)的線程會(huì)在低優(yōu)先級(jí)線程之前得到執(zhí)行。線程調(diào)度器同樣采用“搶占式策略來(lái)調(diào)度線程執(zhí)行,即當(dāng)前線程執(zhí)行過(guò)程中有較高優(yōu)先級(jí)的線程進(jìn)入就緒狀態(tài),那么高優(yōu)先級(jí)的線程立即被調(diào)度執(zhí)行。具有相同優(yōu)先級(jí)的所有線程采用輪轉(zhuǎn)的

26、方式來(lái)共同分配CPU時(shí)間片。在應(yīng)用程序中可以調(diào)用線程對(duì)象的setPriority()方法改變?cè)摼€程的運(yùn)行優(yōu)先級(jí),同樣可以調(diào)用getPriority()方法獲取當(dāng)前線程的優(yōu)先級(jí)。Java 支持10個(gè)優(yōu)先級(jí),基層操作系統(tǒng)支持的優(yōu)先級(jí)可能要少得多,這樣會(huì)造成一些混亂。因此,只能將優(yōu)先級(jí)作為一種很粗略的工具使用,最后的控制可以通過(guò)使用yield()方法來(lái)完成。通常情況下,不要依靠線程優(yōu)先級(jí)來(lái)控制線程的狀態(tài)。 yield方法提供了一個(gè)處理時(shí)間分片問(wèn)題的簡(jiǎn)單方法,如果使用了yield方法,線程會(huì)自動(dòng)釋放處理器,這使得其它線程有時(shí)機(jī)獲得處理器。例9.3使用yield方法重新改寫前面例9.2的程序。/Li9_

27、03.javapublic class Li9_03 implements Runnable int count = 1, number; public Li9_03 (int num) number = num; System.out.println (創(chuàng)立線程 + number); public void run ( ) while (true) System.out.println (線程 + number + :計(jì)數(shù) + count); if(+count = = 6) return; /run public static void main(String args) for(int

28、i = 0; i 10; i+) Li9_03 my = new Li9_03(i+1); Thread tt = new Thread(my); tt.start(); Thread.yield(); /該命令使線程自動(dòng)釋放處理器 9.3 線程的同步線程同步的概念 由于同一進(jìn)程的多個(gè)線程共享同一片存儲(chǔ)空間,在帶來(lái)方便的同時(shí),也帶來(lái)了訪問(wèn)沖突這個(gè)嚴(yán)重的問(wèn)題。Java語(yǔ)言提供了專門機(jī)制以解決這種沖突,有效防止了同一個(gè)數(shù)據(jù)對(duì)象被多個(gè)線程同時(shí)訪問(wèn),這套機(jī)制就是線程同步。線程同步是指Java防止多個(gè)線程同時(shí)訪問(wèn)一個(gè)數(shù)據(jù)而造成數(shù)據(jù)混亂的方法。它可以防止多個(gè)線程同時(shí)訪問(wèn)相同的數(shù)據(jù)產(chǎn)生線程之間的爭(zhēng)搶,防止一

29、個(gè)線程剛生成的數(shù)據(jù)又會(huì)被其他線程生成的數(shù)據(jù)所覆蓋。Java用監(jiān)視器手段來(lái)完成線程的同步。就好似監(jiān)視器把受保護(hù)的資源外面添加了一把鎖,而這把鎖只有一把鑰匙。每一個(gè)線程只有在得到這把鑰匙之后才可以對(duì)被保護(hù)的資源執(zhí)行操作線程,而其它的線程只能等待,直到能拿到這把鑰匙。Java使用關(guān)鍵字synchronized來(lái)實(shí)現(xiàn)多線程的同步,線程同步有兩種實(shí)現(xiàn)方法,一種是方法同步,另一種是對(duì)象同步。9.3 線程的同步方法同步 一個(gè)類中任何方法都可以設(shè)計(jì)成為synchronized方法,以防止多線程數(shù)據(jù)崩潰。當(dāng)一個(gè)線程進(jìn)入synchronized方法后,能保證在其他任何線程訪問(wèn)這個(gè)方法之前完成自己的一次執(zhí)行。如果一

30、個(gè)線程試圖訪問(wèn)一個(gè)已經(jīng)啟動(dòng)的synchronized方法,那么這個(gè)線程必須等待,直到已啟動(dòng)線程執(zhí)行完畢,釋放這個(gè)synchronized方法后才能訪問(wèn)。通過(guò)在方法聲明中參加 synchronized關(guān)鍵字來(lái)聲明 synchronized 方法: public synchronized void methodName(parameterList) 下面將通過(guò)模擬一個(gè)銀行帳號(hào)存取款的操作來(lái)看一下線程同步的重要性。例9.4 模擬銀行中的多個(gè)線程同時(shí)對(duì)同一個(gè)儲(chǔ)蓄賬戶進(jìn)行存款、取款操作。在主程序中我們首先生成了10個(gè)線程,然后啟動(dòng)它們,每一個(gè)線程都對(duì)同一賬戶進(jìn)行存20元,然后馬上又取出10元。這樣,對(duì)于

31、該賬戶來(lái)說(shuō),最終賬戶的余額應(yīng)該是對(duì)原帳戶存款增加1000元才對(duì)。 /多線程的同步舉例。Li9_04.javapublic class Li9_04 implements Runnable Account acc; public Li9_04(Account acc) this.acc = acc; public void run() acc.deposit(20.0f); acc.withdraw(10.0f); /run private static int NUM_OF_THREAD = 100; static Thread threads = new ThreadNUM_OF_THREA

32、D; /創(chuàng)立線程數(shù)組 public static void main(String args) final Account acc = new Account(王紅, 1000.0f); for (int i = 0; i NUM_OF_THREAD; i+) Li9_04 my = new Li9_04(acc); threadsi = new Thread(my); /創(chuàng)立新線程 threadsi.start(); /運(yùn)行線程 for (int i=0; iNUM_OF_THREAD; i+) try threadsi.join(); /等待所有線程運(yùn)行結(jié)束 catch (Interrup

33、tedException e) System.out.println(完成,王紅的帳戶余額為: + acc.getBalance(); /main class Account String name; float amount; public Account(String name, float amount) = name; this.amount = amount; public void deposit(float amt) float tmp = amount; tmp += amt; try Thread.sleep(1);/模擬其它處理所需要的時(shí)間,比方刷新數(shù)據(jù)

34、庫(kù)等 catch (InterruptedException e) amount = tmp; /deposit public void withdraw(float amt) float tmp = amount; tmp -= amt; try Thread.sleep(1);/模擬其它處理所需要的時(shí)間,比方刷新數(shù)據(jù)庫(kù)等 catch (InterruptedException e) amount = tmp; /withdraw public float getBalance() return amount; /getBalance 上面在類Account的deposit和withdraw

35、方法中之所以要把對(duì)amount的運(yùn)算使用一個(gè)臨時(shí)變量首先存儲(chǔ),sleep睡眠一段時(shí)間,然后再賦值給amount,是為了模擬真實(shí)運(yùn)行時(shí)的情況。因?yàn)樵谡鎸?shí)系統(tǒng)中,賬戶信息肯定是存儲(chǔ)在持久媒介中,此處的睡眠時(shí)間相當(dāng)于比較耗時(shí)的數(shù)據(jù)庫(kù)操作,最后把臨時(shí)變量tmp的值賦值給amount相當(dāng)于把a(bǔ)mount的改動(dòng)寫入數(shù)據(jù)庫(kù)中。編譯成功后,運(yùn)行該程序某5次,結(jié)果如下:完成,王紅的帳戶余額為:1160.0完成,王紅的帳戶余額為:1040.0完成,王紅的帳戶余額為:1100.0完成,王紅的帳戶余額為:1100.0完成,王紅的帳戶余額為:1120.0讀者在每次運(yùn)行該程序時(shí)結(jié)果可能與上述結(jié)果也不相同,為什么會(huì)是這樣呢

36、?這是因?yàn)槎嗑€程中的不同步問(wèn)題。在例9.4中,Account類中的amount變量會(huì)同時(shí)被多個(gè)線程所訪問(wèn),它是一個(gè)競(jìng)爭(zhēng)資源,通常稱作競(jìng)態(tài)條件。對(duì)于這樣的多個(gè)線程共享的資源在編寫程序時(shí)必須進(jìn)行同步,以防止一個(gè)線程的改動(dòng)被另一個(gè)線程所覆蓋。amount是一個(gè)競(jìng)態(tài)條件,所有對(duì)amount的修改訪問(wèn)的方法都要進(jìn)行同步,所以應(yīng)該將deposit和withdraw兩個(gè)方法進(jìn)行同步,這兩個(gè)方法分別修改為:public synchronized void deposit(float amt) public synchronized void withdraw(float amt)修改后重新編譯并運(yùn)行該程序,每

37、次運(yùn)行結(jié)果都將得到王紅銀行最后存款余額為2000元,這樣最終結(jié)果才是正確的。從本例可以看到,由于沒(méi)使用synchronized關(guān)鍵字,線程A在對(duì)帳戶操作的同時(shí)其它線程也在對(duì)帳戶進(jìn)行操作,而一個(gè)線程取得的帳戶數(shù)據(jù)是其它線程沒(méi)有提交之前的數(shù)據(jù),所以最后線程在提交自己的數(shù)據(jù)時(shí)將會(huì)覆蓋原來(lái)的數(shù)據(jù),也就相當(dāng)于只執(zhí)行了其中局部線程。在使用synchronized關(guān)鍵字后,將會(huì)使程序中的一個(gè)線程在調(diào)用方法同步的方法時(shí),對(duì)該方法加鎖,此時(shí)其它線程將等待,直到這個(gè)線程提交數(shù)據(jù)后另外一個(gè)線程才能得到方法該方法的鑰匙,即該線程能夠調(diào)用該方法,所以該線程此時(shí)取得的帳戶余額是上一個(gè)線程提交后的帳戶余額。這樣程序才能得到

38、一個(gè)正確結(jié)果,但這些都是對(duì)同步機(jī)制有所理解的前提下進(jìn)行的。對(duì)象同步 synchronized關(guān)鍵字除了可以放在方法聲明中表示整個(gè)方法為同步方法外,還可以放在對(duì)象前面限制一段代碼,當(dāng)某個(gè)對(duì)象用synchronized修飾時(shí),說(shuō)明該對(duì)象在任何一個(gè)時(shí)刻只能由一個(gè)線程訪問(wèn)。例如:synchronized(object) /允許訪問(wèn)控制的代碼方法同步和對(duì)象同步的代碼是可以相互等價(jià)轉(zhuǎn)換的,例如:public synchronized void yourMethod() /修飾方法/ . 與下面對(duì)象同步的代碼效果是一樣的:public void yourMethod ()synchronized(this)

39、 /修飾對(duì)象的引用/ . 有時(shí),一個(gè)方法執(zhí)行時(shí)間很長(zhǎng),而其中只有很短的一段時(shí)間訪問(wèn)關(guān)鍵數(shù)據(jù),在這種情況下,將整個(gè)方法聲明為synchronized,將導(dǎo)致其他線程因無(wú)法調(diào)用該線程的synchronized方法進(jìn)行操作而長(zhǎng)時(shí)間無(wú)法繼續(xù)執(zhí)行,這在整體效率上是不劃算的。此時(shí),就可以使用對(duì)象同步,只把訪問(wèn)關(guān)鍵數(shù)據(jù)的代碼段用花括號(hào)括起來(lái),在其前加上synchronizedthis即可。關(guān)于對(duì)象同步在這里就不再多講,如有興趣的讀者,可以自己把上面的銀行存取款的例子使用對(duì)象同步的方法進(jìn)行修改。 同步方法的缺點(diǎn) 同步機(jī)制雖然很方便,但可能導(dǎo)致死鎖。死鎖是指發(fā)生在線程之間相互阻塞的現(xiàn)象,這種現(xiàn)象導(dǎo)致同步線程相互

40、等待,以致每個(gè)線程都不能往下執(zhí)行。在這種情況下,多個(gè)線程都在等待對(duì)方完成某個(gè)操作,從而產(chǎn)生死鎖現(xiàn)象。例如,一個(gè)線程持有對(duì)象X,另一個(gè)線程持有對(duì)象Y。第一個(gè)線程在擁有對(duì)象X,但必須擁有第二個(gè)線程所持有的對(duì)象Y才能執(zhí)行;同樣,第二個(gè)線程在擁有對(duì)象Y,但必須擁有第一個(gè)線程所持有的對(duì)象X才能執(zhí)行,這樣這兩個(gè)線程就會(huì)無(wú)限期地阻塞,這時(shí),線程就會(huì)出現(xiàn)死鎖。在現(xiàn)實(shí)程序中,錯(cuò)誤的同步往往會(huì)出現(xiàn)死鎖,而且是較難發(fā)現(xiàn)的。這就像兩個(gè)人只有一雙筷子使用時(shí),每個(gè)人拿到了一根筷子,而兩個(gè)人卻都想得到對(duì)方的筷子,這時(shí)就產(chǎn)生了死鎖,兩人都無(wú)法得到所需的資源。為了防止死鎖問(wèn)題,在進(jìn)行多線程程序設(shè)計(jì)時(shí)需要遵循如下原那么:在指定的

41、任務(wù)真正需要并行時(shí)才采用多線程來(lái)進(jìn)行程序設(shè)計(jì);在對(duì)象的同步方法中需要調(diào)用其他同步方法時(shí)必須小心;在synchronized封裝的塊中的時(shí)間盡可能的短,需要長(zhǎng)時(shí)間運(yùn)行的任務(wù)盡量不要放在synchronized封裝的同步塊中。另外,假設(shè)將一個(gè)大的方法聲明為synchronized 將會(huì)大大影響效率。典型地,如果一個(gè)方法執(zhí)行時(shí)間很長(zhǎng),而其中只有很短的一段時(shí)間訪問(wèn)關(guān)鍵數(shù)據(jù),在這種情況下,將整個(gè)方法聲明為synchronized,將導(dǎo)致其他線程因無(wú)法調(diào)用該線程的其他synchronized方法進(jìn)行操作而長(zhǎng)時(shí)間無(wú)法繼續(xù)執(zhí)行,這將在很大程度上降低程序的運(yùn)行效率。9.4 線程組 線程組 線程組Thread g

42、roup是包括了許多線程的對(duì)象集,線程組擁有一個(gè)名字以及與它相關(guān)的一些屬性,可以用于作為一個(gè)組來(lái)管理其中的線程。線程組能夠有效組織JVM的線程,并且可以提供一些組間的平安性。在Java中,所有線程和線程組都隸屬于一個(gè)線程組,可以是一個(gè)默認(rèn)線程組,亦可是一個(gè)創(chuàng)立線程時(shí)明確指定的組。在創(chuàng)立之初,線程被限制到一個(gè)組里,而且不能改變到另外的組。假設(shè)創(chuàng)立多個(gè)線程而不指定一個(gè)組,它們就會(huì)自動(dòng)歸屬于系統(tǒng)線程組。這樣,所有線程組和線程組成了一棵以系統(tǒng)線程組為根的樹(shù)。如圖9.4所示。 圖9.4 Java線程組層次例如圖Thread類中提供了構(gòu)造方法使創(chuàng)立線程時(shí)同時(shí)決定其線程組。本章第2節(jié)介紹了Thread類提供

43、六種構(gòu)造方法,前四種缺省了線程組,表示所創(chuàng)立的線程屬于main線程組,后兩種那么指定了所創(chuàng)立線程的線程組。線程可以訪問(wèn)自己所在的線程組,但不能訪問(wèn)本線程組的父類。對(duì)線程組進(jìn)行操作就是對(duì)線程組中的各個(gè)線程同時(shí)進(jìn)行操作。Java的ThreadGroup類提供了一些方法來(lái)方便我們對(duì)線程組樹(shù)中的線程組和線程進(jìn)行操作,比方可以通過(guò)調(diào)用線程組的相應(yīng)方法來(lái)設(shè)置其中所有線程的優(yōu)先級(jí),也可以啟動(dòng)或阻塞其中的所有線程。 ThreadGroup類 Java的線程組由包java.lang中的類ThreadGroup實(shí)現(xiàn)。在生成線程時(shí),可以指定將線程放在某個(gè)線程組中,也可以由系統(tǒng)將它放在某個(gè)缺省的線程組中。通常,缺省的

44、線程組就是生成該線程所在的線程組。但是,一旦某個(gè)線程參加某個(gè)線程組,它將一直是這個(gè)線程組的成員,而不能被移出這個(gè)線程組。在創(chuàng)立線程之前,可以創(chuàng)立一個(gè)ThreadGroup對(duì)象,下面代碼創(chuàng)立線程組并在其中參加兩個(gè)線程:ThreadGroup yourThreadGroup = new ThreadGroupx;Thread yourThread1 = new ThreadyourThreadGroup,worker1;Thread yourThread2 = new ThreadyourThreadGroup,worker2;yourThread1.start;yourThread2.start

45、;如上例所示,首先創(chuàng)立一個(gè)線程組,然后創(chuàng)立兩個(gè)線程,并傳遞給ThreadGroup對(duì)象,稱為線程組中的成員。每一個(gè)線程可以各自調(diào)用start( )方法啟動(dòng)。ThreadGroup類并不提供一次啟動(dòng)所有線程的start()方法,但是可以通過(guò)調(diào)整線程組的優(yōu)先級(jí)來(lái)掛起或者運(yùn)行某個(gè)線程組中的所有線程。類ThreadGroup提供了一些方法對(duì)線程組中的線程和子線程組進(jìn)行操作,下面將對(duì)這些方法進(jìn)行介紹。 getName():返回線程組的名字。 getParent():返回該線程的父線程組的名稱。 activeCount():返回線程組中當(dāng)前激活的線程的數(shù)目,包括子線程組中的活動(dòng)線程。enumerate(T

46、hread ):將所有該線程組中激活的線程復(fù)制到一個(gè)特殊的數(shù)組中。setMaxPriority(int pri):設(shè)置線程組的最高優(yōu)先級(jí),pri是該線程組的新優(yōu)先級(jí),優(yōu)先級(jí)從110依次增高。getMaxPriority():返回線程在線程組中擁有的最高優(yōu)先級(jí)。getTheradGroup():返回線程組。interrupt():向線程組及其子組中的線程發(fā)送一個(gè)中斷信息。 setDaemon(booleam daemon):將該線程設(shè)置為Daemon(守護(hù),即常駐內(nèi)存)狀態(tài)。isDaemon():判斷是否是Daemon線程組。isDestroyed():判斷線程組是否已經(jīng)被銷毀。parentOf

47、(ThreadGroup g):判斷線程組是否是線程組g或g的子線程組。list():對(duì)每個(gè)Thread調(diào)用toString。toString():返回一個(gè)表示本線程組的字符串,包括它的名字和最高線程優(yōu)先級(jí)。另外,ThreadGroup類還提供了幾個(gè)方法用來(lái)改變線程組中的所有線程的當(dāng)前狀態(tài),如resume()、stop()、suspend(),但這些方法已不鼓勵(lì)使用,對(duì)線程組的掛起或運(yùn)行建議通過(guò)優(yōu)先級(jí)的調(diào)整和讓線程睡眠(sleep)來(lái)實(shí)現(xiàn)。例9.5 利用線程組的各種方法演示線程的根本用法。/演示線程的根本用法。Li9_05.javapublic class Li9_05public void

48、test( ) ThreadGroup tg = new ThreadGroup(test);/創(chuàng)立名稱為test的tg線程組Thread A = new Thread(tg,A); /創(chuàng)立線程,并設(shè)置該線程屬于tg線程組 Thread B = new Thread(tg,B); Thread C = new Thread(tg,C); A.setPriority(6); /設(shè)置線程A的優(yōu)先級(jí)為6 C.setPriority(4); /設(shè)置線程C的優(yōu)先級(jí)為4,線程B默認(rèn)為5 System.out.println(tg線程組正在活動(dòng)的線程個(gè)數(shù):+tg.activeCount(); System.

49、out.println(線程A的優(yōu)先級(jí)是:+A.getPriority(); tg.setMaxPriority(8); /設(shè)置線程組tg的優(yōu)先級(jí) System.out.println(tg線程組的優(yōu)先級(jí)是:+tg.getMaxPriority(); System.out.println(tg線程組上一級(jí)線程組信息:+tg.getParent(); System.out.println(線程組名稱:+tg.getName(); System.out.print(tg線程組的信息:); tg.list( ); /調(diào)用toString()方法,返回線程組的名稱和優(yōu)先級(jí) /test( )public

50、 static void main(String argv)Li9_05 ttg = new Li9_05( ); /構(gòu)造函數(shù)ttgttg.test( ); /運(yùn)行函數(shù)ttg中的test方法 線程之間的通訊 多線程的一個(gè)重要特點(diǎn)是它們之間可以互相通訊,線程通信使線程之間可以相互交流和等待,可以通過(guò)經(jīng)常共享的數(shù)據(jù)使線程相互交流,也可以通過(guò)線程控制方法使線程相互等待。Object類為此提供了3個(gè)函數(shù):wait()、notify()和notifyAll()。典型的線程間通訊建立在生產(chǎn)者和消費(fèi)者模型上:一個(gè)線程產(chǎn)生輸出;另一個(gè)線程使用輸入,先有生產(chǎn)者生產(chǎn),才能有消費(fèi)者消費(fèi)。生產(chǎn)者未生產(chǎn)之前,通知消費(fèi)者

51、等待;生產(chǎn)后通知消費(fèi)者消費(fèi),消費(fèi)者消費(fèi)以后再通知生產(chǎn)者,這就是等待通知機(jī)制(wait/notify)。例9.6說(shuō)明了這個(gè)模型。所謂生產(chǎn)者(producer)是產(chǎn)生一串?dāng)?shù)據(jù)流的線程,而消費(fèi)者(Consumer)是消耗流中數(shù)據(jù)的線程。例如,當(dāng)在鍵盤上敲入字符串,生產(chǎn)者線程把鍵盤事件放入事件隊(duì)列,消費(fèi)者線程從該隊(duì)列讀出事件。 例9.6 著名的生產(chǎn)者消費(fèi)者通訊模型。本模型中兩個(gè)并行線程共享同一個(gè)資源,生產(chǎn)者類產(chǎn)生09之間的整數(shù),存入一個(gè)“Store對(duì)象,顯示這個(gè)整數(shù),還休息一段隨機(jī)時(shí)間消費(fèi)者類那么快速地消耗從Store剛得到的整數(shù)。Producer和Consumer共同訪問(wèn)Store對(duì)象,假設(shè)兩者沒(méi)有

52、采取同步措施,運(yùn)行結(jié)果可能出現(xiàn)兩種現(xiàn)象:一是當(dāng)Producer比Consumer快時(shí),生產(chǎn)了兩個(gè)數(shù)才消費(fèi)了一個(gè);二是當(dāng)Consumer比Producer快時(shí),只生產(chǎn)出一個(gè)數(shù)而消費(fèi)了兩個(gè)數(shù)。這種現(xiàn)象就是競(jìng)爭(zhēng)條件,解決的方法就是采用條件變量。本例的條件變量是Store對(duì)象,對(duì)它的關(guān)鍵局部用synchronized關(guān)鍵字標(biāo)識(shí)。具體的生產(chǎn)者消費(fèi)者問(wèn)題的java算法可以用下面的代碼描述。/生產(chǎn)者消費(fèi)者通訊模型。Li9_06.java本例中Store類在get()和put()前標(biāo)識(shí)了synchronized關(guān)鍵字,稱為同步方法。擁有同步方法的Store類對(duì)象有惟一的一個(gè)監(jiān)視器。當(dāng)Producer調(diào)用Sto

53、re類的put()方法,生產(chǎn)者獲得監(jiān)視器,防止消費(fèi)者調(diào)用Store類的get()方法,這就是鎖定。只有put()方法返回,Producer釋放監(jiān)視器,才解鎖Store對(duì)象。同理,當(dāng)Consumer調(diào)用Store類的get()方法時(shí),消費(fèi)者獲得監(jiān)視器,防止生產(chǎn)者調(diào)用Store類的put()方法。這樣就保證了Producer與Consumer兩具線程的同步運(yùn)行,即生產(chǎn)一個(gè)消費(fèi)一個(gè)。本例中的put()方法和get()方法都采用了wait()方法和notify()方法進(jìn)行同步。假設(shè)生產(chǎn)者沒(méi)有產(chǎn)生數(shù),available=false,調(diào)用get()方法的Consumer線程進(jìn)入wait狀態(tài),等Produc

54、er線程放好數(shù)后令available=true,并用notify()方法喚醒Consumer取數(shù)。假設(shè)消費(fèi)者沒(méi)有取走數(shù),available=true,調(diào)用put()方法的Producer線程進(jìn)入wait狀態(tài),等Consumer線程取走數(shù)后令available=false,并用notify()方法喚醒Producer放數(shù)。Java是第一個(gè)在語(yǔ)言本身中顯式地包含線程的主流編程語(yǔ)言,而多線程的優(yōu)點(diǎn)是可以合理地調(diào)配多個(gè)任務(wù),但在多個(gè)線程需要共享數(shù)據(jù)或共享存儲(chǔ)結(jié)構(gòu)時(shí)會(huì)產(chǎn)生執(zhí)行結(jié)果的不確定性,甚至可能造成程序出現(xiàn)錯(cuò)誤,所以要利用Java提供的同步機(jī)制控制互相交互的線程之間的運(yùn)行進(jìn)度,使線程執(zhí)行時(shí)不出現(xiàn)錯(cuò)誤

55、結(jié)果。huHV-boBO!5ivIV-bpCP$5ivJW+cpCP$6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0drER&7kxLY1erER*8lyLY1fsFS*8lIW+cpCP$6jwJW+cqDQ%6jwJX0dqDQ%7kxKX0drER&7kxLY1erER&8lyLY1esFS*8lyMZ2fsFS(9mzMZ2ftGT(9mzN#3gtGT)anAN#3huHU)anAO!4huHU-boBO!4ivIV-boCP$5ivIV+cpCP$5jwJW+cpDQ%6jwJW0dqDQ%6kxKX0dqER&7kxKY1erER&7lyLGT(9mAN#3gtGT)a

56、nAN#3huHU)anBO!4huHV-boBO!4ivIV-boCP$5ivIW+cpCP$6jwJW+cpDQ%6jwJX0dqDQ%7kxKX0dqER&7kxKY1erER&8lyLY1esFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzN#3gtGT(anAN#3guHU)anAO!4huHU-boBO!4hvIV-boBP$5iLY2fsFS*8mzMZ2fsGT(9mzMZ3gtGT(9nAN#3gtHU)anAN!4huHU)aoBO!4huIV-boBO$5ivIV-bpCP$5ivJW+cpCP%6jwJW+dqDQ%6jwKX0dqDQ&7kxKX0erER&7

57、kyLY1erER*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ2gtGT(9mAN#3gtGU)anAN#4huHU)anBO!4xKY1erER&8lyLY1esFS*8lyMZ2fsFS*9mzMZ2ftGT(9mzN#3gtGT)anAN#3guHU)anAO!4huHU-boBO!4hvIV-boBP$5ivIV+cpCP$5jwJW+cpCQ%6jwJW0dqDQ%6kxKX0dqER&7kxKX1erER&7lyLY1erFS*8lyLZ2fsFS*8mzMZ2fsGT(9mzM#3gtGTO!4ivIV-boCP$5ivIV+cpCP$5jwJW+cpDQ%6jwJX

58、0dqDQ%6kxKX0dqER&7kxKY1erER&8lyLY1erFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzM#3gtGT(anAN#3guHU)anAO!4huHU)boBO!4hvIV-boBP$5ivIV+cpCP$5iwJW+cpCQ%6jwJW0dqDQ%6AN#3gtGU)anAN#4huHU)aoBO!4huIV-boBO!5ivIV-bpCP$5ivJW+cpCP%6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0erER&7kxLY1erER*8lyLY1fsFS*8lzMZ2fsFS(9mzMZ2gtGT(9mAN#3gtGU)anAN#3huHU

59、)anBO!4huHV-boBO!5ivIVQ%6jxKX0dqDQ&7kxKX0erER&7kyLY1erES*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ3gtGT(9mAN#3gtGU)anAN#4huHU)aoBO!4tGU)anAN6kxKX0dqER&7kxKY1erER&8lyLY1erFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzM#3gtGT(anAN#3guHU)anAO!4huHU)boBO!4hvIV-boBP$5ivIV+cpCP$5iwJW+cpCQ%6jwJW0dqDQ%6kxKX0dqDR&7kxKX1erER&7lyLY1erFS*8lyL

60、YT)anAN#3guHU)anAO!4huHU-boBO!4ivIV-boBP$5ivIV+cpCP$5jwJW+cpDQ%6jwJW0dqDQ%6kxKX0dqER&7kxKY1erER&7lyLY1erFS*8lyLZ2fsFS*9mzMZ2fsGT(9mzM#3gtGT(anAN#3guHU)anAN!4huHU)boBO!kyLY1erES*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ3gtGT(9mAN#3gtGU)anAN#4huHU)aoBO!4huHV-boBO!5ivIV-bpCP$5ivJW+cpCP$6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0dr

溫馨提示

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

評(píng)論

0/150

提交評(píng)論