




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
Java多線程編程線程總結(jié)Java線程是Java語言中一個(gè)非常重要的部分,Java5之前,多線程的語言支持還是比較弱的,內(nèi)容也較少,寫一個(gè)復(fù)雜的多線程程序是相當(dāng)有挑戰(zhàn)性的。Java5以后,Java對(duì)多線程做了很多擴(kuò)展,擴(kuò)展部分稱之為并發(fā)包。這部分內(nèi)容大大增強(qiáng)Java多線程編程的能力,通過使用Java5線程新特征的API,可以很容易的做出復(fù)雜的多線程程序。與其他語言相比,已經(jīng)是相當(dāng)強(qiáng)悍了。通過十多篇博文,將Java多線程的方方面面過了一遍,對(duì)我來說是一個(gè)學(xué)習(xí)和提高的過程,也為Java線程系列博文能給后來的學(xué)習(xí)者帶來便利。知識(shí)點(diǎn)都過了一遍,要總結(jié)起來感覺很困難,畢竟Java線程是一個(gè)龐大的話題,不知道從何說起,如果泛泛而談,那總結(jié)還有什么意義呢,再次,將前面的博文串聯(lián)起來,按照先后順序加上鏈接,以方便瀏覽也最好的首尾。下面是Java線程系列博文的一個(gè)編目:Java線程:概念與原理Java線程:創(chuàng)建與啟動(dòng)Java線程:線程棧模型與線程的變量Java線程:線程狀態(tài)的轉(zhuǎn)換Java線程:線程的同步與鎖Java線程:線程的交互Java線程:線程的調(diào)度-休眠Java線程:線程的調(diào)度-優(yōu)先級(jí)Java線程:線程的調(diào)度-讓步Java線程:線程的調(diào)度-合并Java線程:線程的調(diào)度-守護(hù)線程Java線程:線程的同步-同步方法Java線程:線程的同步-同步塊Java線程:并發(fā)協(xié)作-生產(chǎn)者消費(fèi)者模型Java線程:并發(fā)協(xié)作-死鎖Java線程:volatile關(guān)鍵字Java線程:新特征-線程池Java線程:新特征-有返回值的線程Java線程:新特征-鎖(上)Java線程:新特征-鎖(下)Java線程:新特征-信號(hào)量Java線程:新特征-阻塞隊(duì)列Java線程:新特征-阻塞棧Java線程:新特征-條件變量Java線程:新特征-原子量Java線程:新特征-障礙器Java線程:大總結(jié)Java線程:概念與原理一、操作系統(tǒng)中線程和進(jìn)程的概念現(xiàn)在的操作系統(tǒng)是多任務(wù)操作系統(tǒng)。多線程是實(shí)現(xiàn)多任務(wù)的一種方式。進(jìn)程是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序,每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以啟動(dòng)多個(gè)線程。比如在Windows系統(tǒng)中,一個(gè)運(yùn)行的exe就是一個(gè)進(jìn)程。線程是指進(jìn)程中的一個(gè)執(zhí)行流程,一個(gè)進(jìn)程中可以運(yùn)行多個(gè)線程。比如java.exe進(jìn)程中可以運(yùn)行很多線程。線程總是屬于某個(gè)進(jìn)程,進(jìn)程中的多個(gè)線程共享進(jìn)程的內(nèi)存?!巴瑫r(shí)”執(zhí)行是人的感覺,在線程之間實(shí)際上輪換執(zhí)行。二、Java中的線程Java中,“線程”指兩件不同的事情:1、java.lang.Thread類的一個(gè)實(shí)例;2、線程的執(zhí)行。使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實(shí)例化和啟動(dòng)新線程。一個(gè)Thread類實(shí)例只是一個(gè)對(duì)象,像Java中的任何其他對(duì)象一樣,具有變量和方法,生死于堆上。Java中,每個(gè)線程都有一個(gè)調(diào)用棧,即使不在程序中創(chuàng)建任何新的線程,線程也在后臺(tái)運(yùn)行著。一個(gè)Java應(yīng)用總是從main()方法開始運(yùn)行,mian()方法運(yùn)行在一個(gè)線程內(nèi),它被稱為主線程。一旦創(chuàng)建一個(gè)新的線程,就產(chǎn)生一個(gè)新的調(diào)用棧。線程總體分兩類:用戶線程和守候線程。當(dāng)所有用戶線程執(zhí)行完畢的時(shí)候,JVM自動(dòng)關(guān)閉。但是守候線程卻不獨(dú)立于JVM,守候線程一般是由操作系統(tǒng)或者用戶自己創(chuàng)建的。Java線程:創(chuàng)建與啟動(dòng)一、定義線程1、擴(kuò)展java.lang.Thread類。此類中有個(gè)run()方法,應(yīng)該注意其用法:publicvoidrun()如果該線程是使用獨(dú)立的Runnable運(yùn)行對(duì)象構(gòu)造的,則調(diào)用該Runnable對(duì)象的run方法;否則,該方法不執(zhí)行任何操作并返回。Thread的子類應(yīng)該重寫該方法。2、實(shí)現(xiàn)java.lang.Runnable接口。voidrun()使用實(shí)現(xiàn)接口Runnable的對(duì)象創(chuàng)建一個(gè)線程時(shí),啟動(dòng)該線程將導(dǎo)致在獨(dú)立執(zhí)行的線程中調(diào)用對(duì)象的run方法。方法run的常規(guī)協(xié)定是,它可能執(zhí)行任何所需的操作。二、實(shí)例化線程1、如果是擴(kuò)展java.lang.Thread類的線程,則直接new即可。2、如果是實(shí)現(xiàn)了java.lang.Runnable接口的類,則用Thread的構(gòu)造方法:Thread(Runnabletarget)Thread(Runnabletarget,Stringname)Thread(ThreadGroupgroup,Runnabletarget)Thread(ThreadGroupgroup,Runnabletarget,Stringname)Thread(ThreadGroupgroup,Runnabletarget,Stringname,longstackSize)三、啟動(dòng)線程在線程的Thread對(duì)象上調(diào)用start()方法,而不是run()或者別的方法。在調(diào)用start()方法之前:線程處于新狀態(tài)中,新狀態(tài)指有一個(gè)Thread對(duì)象,但還沒有一個(gè)真正的線程。在調(diào)用start()方法之后:發(fā)生了一系列復(fù)雜的事情:啟動(dòng)新的執(zhí)行線程(具有新的調(diào)用棧);該線程從新狀態(tài)轉(zhuǎn)移到可運(yùn)行狀態(tài);當(dāng)該線程獲得機(jī)會(huì)執(zhí)行時(shí),其目標(biāo)run()方法將運(yùn)行。注意:對(duì)Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調(diào)用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調(diào)用run方法是合法的。但并不啟動(dòng)新的線程。四、例子1、實(shí)現(xiàn)Runnable接口的多線程例子/**實(shí)現(xiàn)Runnable接口的類@authorleizhimin2008-9-1318:12:10*/publicclassDoSomethingimplementsRunnable{privateStringname;publicDoSomething(Stringname){=name;}publicvoidrun(){for(inti=0;i<5;i++){for(longk=0;k<100000000;k++);System.out.println(name+":"+i);}}}/**測試Runnable類實(shí)現(xiàn)的多線程程序@authorleizhimin2008-9-1318:15:02*/publicclassTestRunnable{publicstaticvoidmain(String[]args){DoSomethingds1=newDoSomething("阿三");DoSomethingds2=newDoSomething("李四");Threadt1=newThread(ds1);Threadt2=newThread(ds2);t1.start();t2.start();}}執(zhí)行結(jié)果:李四:0阿三:0李四:1阿三:1李四:2李四:3阿三:2李四:4阿三:3阿三:4Processfinishedwithexitcode02、擴(kuò)展Thread類實(shí)現(xiàn)的多線程例子/**測試擴(kuò)展Thread類實(shí)現(xiàn)的多線程程序@authorleizhimin2008-9-1318:22:13*/publicclassTestThreadextendsThread{publicTestThread(Stringname){super(name);}publicvoidrun(){for(inti=0;i<5;i++){for(longk=0;k<100000000;k++);System.out.println(this.getName()+":"+i);}}publicstaticvoidmain(String[]args){Threadt1=newTestThread("阿三");Threadt2=newTestThread("李四");t1.start();t2.start();}}執(zhí)行結(jié)果:阿三:0李四:0阿三:1李四:1阿三:2李四:2阿三:3阿三:4李四:3李四:4Processfinishedwithexitcode0對(duì)于上面的多線程程序代碼來說,輸出的結(jié)果是不確定的。其中的一條語句for(longk=0;k<100000000;k++);是用來模擬一個(gè)非常耗時(shí)的操作的。五、一些常見問題1、線程的名字,一個(gè)運(yùn)行中的線程總是有名字的,名字有兩個(gè)來源,一個(gè)是虛擬機(jī)自己給的名字,一個(gè)是你自己的定的名字。在沒有指定線程名字的情況下,虛擬機(jī)總會(huì)為線程指定名字,并且主線程的名字總是mian,非主線程的名字不確定。2、線程都可以設(shè)置名字,也可以獲取線程的名字,連主線程也不例外。3、獲取當(dāng)前線程的對(duì)象的方法是:Thread.currentThread();4、在上面的代碼中,只能保證:每個(gè)線程都將啟動(dòng),每個(gè)線程都將運(yùn)行直到完成。一系列線程以某種順序啟動(dòng)并不意味著將按該順序執(zhí)行。對(duì)于任何一組啟動(dòng)的線程來說,調(diào)度程序不能保證其執(zhí)行次序,持續(xù)時(shí)間也無法保證。5、當(dāng)線程目標(biāo)run()方法結(jié)束時(shí)該線程完成。6、一旦線程啟動(dòng),它就永遠(yuǎn)不能再重新啟動(dòng)。只有一個(gè)新的線程可以被啟動(dòng),并且只能一次。一個(gè)可運(yùn)行的線程或死線程可以被重新啟動(dòng)。7、線程的調(diào)度是JVM的一部分,在一個(gè)CPU的機(jī)器上上,實(shí)際上一次只能運(yùn)行一個(gè)線程。一次只有一個(gè)線程棧執(zhí)行。JVM線程調(diào)度程序決定實(shí)際運(yùn)行哪個(gè)處于可運(yùn)行狀態(tài)的線程。眾多可運(yùn)行線程中的某一個(gè)會(huì)被選中做為當(dāng)前線程。可運(yùn)行線程被選擇運(yùn)行的順序是沒有保障的。8、盡管通常采用隊(duì)列形式,但這是沒有保障的。隊(duì)列形式是指當(dāng)一個(gè)線程完成“一輪”時(shí),它移到可運(yùn)行隊(duì)列的尾部等待,直到它最終排隊(duì)到該隊(duì)列的前端為止,它才能被再次選中。事實(shí)上,我們把它稱為可運(yùn)行池而不是一個(gè)可運(yùn)行隊(duì)列,目的是幫助認(rèn)識(shí)線程并不都是以某種有保障的順序排列唱呢個(gè)一個(gè)隊(duì)列的事實(shí)。9、盡管我們沒有無法控制線程調(diào)度程序,但可以通過別的方式來影響線程調(diào)度的方式。Java線程:線程棧模型與線程的變量要理解線程調(diào)度的原理,以及線程執(zhí)行過程,必須理解線程棧模型。線程棧是指某時(shí)刻時(shí)內(nèi)存中線程調(diào)度的棧信息,當(dāng)前調(diào)用的方法總是位于棧頂。線程棧的內(nèi)容是隨著程序的運(yùn)行動(dòng)態(tài)變化的,因此研究線程棧必須選擇一個(gè)運(yùn)行的時(shí)刻(實(shí)際上指代碼運(yùn)行到什么地方)。下面通過一個(gè)示例性的代碼說明線程(調(diào)用)棧的變化過程。這幅圖描述在代碼執(zhí)行到兩個(gè)不同時(shí)刻1、2時(shí)候,虛擬機(jī)線程調(diào)用棧示意圖。當(dāng)程序執(zhí)行到t.start();時(shí)候,程序多出一個(gè)分支(增加了一個(gè)調(diào)用棧B),這樣,棧A、棧B并行執(zhí)行。從這里就可以看出方法調(diào)用和線程啟動(dòng)的區(qū)別了。Java線程:線程狀態(tài)的轉(zhuǎn)換一、線程狀態(tài)線程的狀態(tài)轉(zhuǎn)換是線程控制的基礎(chǔ)。線程狀態(tài)總的可分為五大狀態(tài):分別是生、死、可運(yùn)行、運(yùn)行、等待/阻塞。用一個(gè)圖來描述如下:1、新狀態(tài):線程對(duì)象已經(jīng)創(chuàng)建,還沒有在其上調(diào)用start()方法。2、可運(yùn)行狀態(tài):當(dāng)線程有資格運(yùn)行,但調(diào)度程序還沒有把它選定為運(yùn)行線程時(shí)線程所處的狀態(tài)。start()方法調(diào)用時(shí),線程首先進(jìn)入可運(yùn)行狀態(tài)。在線程運(yùn)行之后或者從阻塞、等待或睡眠狀態(tài)回來后,也返回到可運(yùn)行狀態(tài)。3、運(yùn)行狀態(tài):線程調(diào)度程序從可運(yùn)行池中選擇一個(gè)線程作為當(dāng)前線程時(shí)線程所處的狀態(tài)。這也是線程進(jìn)入運(yùn)行狀態(tài)的唯一一種方式。4、等待/阻塞/睡眠狀態(tài):這是線程有資格運(yùn)行時(shí)它所處的狀態(tài)。實(shí)際上這個(gè)三狀態(tài)組合為一種,其共同點(diǎn)是:線程仍舊是活的,但是當(dāng)前沒有條件運(yùn)行。換句話說,它是可運(yùn)行的,但是如果某件事件出現(xiàn),他可能返回到可運(yùn)行狀態(tài)。5、死亡態(tài):當(dāng)線程的run()方法完成時(shí)就認(rèn)為它死去。這個(gè)線程對(duì)象也許是活的,但是,它已經(jīng)不是一個(gè)單獨(dú)執(zhí)行的線程。線程一旦死亡,就不能復(fù)生。如果在一個(gè)死去的線程上調(diào)用start()方法,會(huì)拋出java.lang.IllegalThreadStateException異常。有關(guān)詳細(xì)狀態(tài)轉(zhuǎn)換圖可以參看下圖。二、阻止線程執(zhí)行對(duì)于線程的阻止,考慮一下三個(gè)方面,不考慮IO阻塞的情況:睡眠;等待;因?yàn)樾枰粋€(gè)對(duì)象的鎖定而被阻塞。1、睡眠Thread.sleep(longmillis)和Thread.sleep(longmillis,intnanos)靜態(tài)方法強(qiáng)制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),以“減慢線程”。當(dāng)線程睡眠時(shí),它入睡在某個(gè)地方,在蘇醒之前不會(huì)返回到可運(yùn)行狀態(tài)。當(dāng)睡眠時(shí)間到期,則返回到可運(yùn)行狀態(tài)。線程睡眠的原因:線程執(zhí)行太快,或者需要強(qiáng)制進(jìn)入下一輪,因?yàn)镴ava規(guī)范不保證合理的輪換。睡眠的實(shí)現(xiàn):調(diào)用靜態(tài)方法。try{Thread.sleep(123);}catch(InterruptedExceptione){e.printStackTrace();}睡眠的位置:為了讓其他線程有機(jī)會(huì)執(zhí)行,可以將Thread.sleep()的調(diào)用放線程run()之內(nèi)。這樣才能保證該線程執(zhí)行過程中會(huì)睡眠。例如,在前面的例子中,將一個(gè)耗時(shí)的操作改為睡眠,以減慢線程的執(zhí)行??梢赃@么寫:publicvoidrun(){for(inti=0;i<5;i++){很耗時(shí)的操作,用來減慢線程的執(zhí)行for(longk=0;k<100000000;k++);try{Thread.sleep(3);}catch(InterruptedExceptione){e.printStackTrace();.}System.out.println(this.getName()+":"+i);}}運(yùn)行結(jié)果:阿三:0李四:0阿三:1阿三:2阿三:3李四:1李四:2阿三:4李四:3李四:4Processfinishedwithexitcode0這樣,線程在每次執(zhí)行過程中,總會(huì)睡眠3毫秒,睡眠了,其他的線程就有機(jī)會(huì)執(zhí)行了。注意:1、線程睡眠是幫助所有線程獲得運(yùn)行機(jī)會(huì)的最好方法。2、線程睡眠到期自動(dòng)蘇醒,并返回到可運(yùn)行狀態(tài),不是運(yùn)行狀態(tài)。sleep()中指定的時(shí)間是線程不會(huì)運(yùn)行的最短時(shí)間。因此,sleep()方法不能保證該線程睡眠到期后就開始執(zhí)行。3、sleep()是靜態(tài)方法,只能控制當(dāng)前正在運(yùn)行的線程。下面給個(gè)例子:/**一個(gè)計(jì)數(shù)器,計(jì)數(shù)到100,在每個(gè)數(shù)字之間暫停1秒,每隔10個(gè)數(shù)字輸出一個(gè)字符串@authorleizhimin2008-9-149:53:49*/publicclassMyThreadextendsThread{publicvoidrun(){for(inti=0;i<100;i++){if((i)%10==0){System.out.println("-------"+i);}System.out.print(i);try{Thread.sleep(1);System.out.print(" 線程睡眠1毫秒!\n");}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticvoidmain(String[]args){newMyThread().start();}}-------0線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!-------10線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!線程睡眠1毫秒!…….2、線程的優(yōu)先級(jí)和線程讓步y(tǒng)ield()線程的讓步是通過Thread.yield()來實(shí)現(xiàn)的。yield()方法的作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。要理解yield(),必須了解線程的優(yōu)先級(jí)的概念。線程總是存在優(yōu)先級(jí),優(yōu)先級(jí)范圍在1~10之間。JVM線程調(diào)度程序是基于優(yōu)先級(jí)的搶先調(diào)度機(jī)制。在大多數(shù)情況下,當(dāng)前運(yùn)行的線程優(yōu)先級(jí)將大于或等于線程池中任何線程的優(yōu)先級(jí)。但這僅僅是大多數(shù)情況。注意:當(dāng)設(shè)計(jì)多線程應(yīng)用程序的時(shí)候,一定不要依賴于線程的優(yōu)先級(jí)。因?yàn)榫€程調(diào)度優(yōu)先級(jí)操作是沒有保障的,只能把線程優(yōu)先級(jí)作用作為一種提高程序效率的方法,但是要保證程序不依賴這種操作。當(dāng)線程池中線程都具有相同的優(yōu)先級(jí),調(diào)度程序的JVM實(shí)現(xiàn)自由選擇它喜歡的線程。這時(shí)候調(diào)度程序的操作有兩種可能:一是選擇一個(gè)線程運(yùn)行,直到它阻塞或者運(yùn)行完成為止。二是時(shí)間分片,為池內(nèi)的每個(gè)線程提供均等的運(yùn)行機(jī)會(huì)。設(shè)置線程的優(yōu)先級(jí):線程默認(rèn)的優(yōu)先級(jí)是創(chuàng)建它的執(zhí)行線程的優(yōu)先級(jí)。可以通過setPriority(intnewPriority)更改線程的優(yōu)先級(jí)。例如:Threadt=newMyThread();t.setPriority(8);t.start();線程優(yōu)先級(jí)為1~10之間的正整數(shù),JVM從不會(huì)改變一個(gè)線程的優(yōu)先級(jí)。然而,1~10之間的值是沒有保證的。一些JVM可能不能識(shí)別10個(gè)不同的值,而將這些優(yōu)先級(jí)進(jìn)行每兩個(gè)或多個(gè)合并,變成少于10個(gè)的優(yōu)先級(jí),則兩個(gè)或多個(gè)優(yōu)先級(jí)的線程可能被映射為一個(gè)優(yōu)先級(jí)。線程默認(rèn)優(yōu)先級(jí)是5,Thread類中有三個(gè)常量,定義線程優(yōu)先級(jí)范圍:staticintMAX_PRIORITY線程可以具有的最高優(yōu)先級(jí)。staticintMIN_PRIORITY線程可以具有的最低優(yōu)先級(jí)。staticintNORM_PRIORITY分配給線程的默認(rèn)優(yōu)先級(jí)。3、Thread.yield()方法Thread.yield()方法作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)。因此,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。4、join()方法Thread的非靜態(tài)方法join()讓一個(gè)線程B“加入”到另外一個(gè)線程A的尾部。在A執(zhí)行完畢之前,不能工作。例如:Threadt=newMyThread();t.start();t.join();另外,join()方法還有帶超時(shí)限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,如果超過這個(gè)時(shí)間,則停止等待,變?yōu)榭蛇\(yùn)行狀態(tài)。線程的加入join()對(duì)線程棧導(dǎo)致的結(jié)果是線程棧發(fā)生了變化,當(dāng)然這些變化都是瞬時(shí)的。下面給示意圖:小結(jié)到目前為止,介紹了線程離開運(yùn)行狀態(tài)的3種方法:1、調(diào)用Thread.sleep():使當(dāng)前線程睡眠至少多少毫秒(盡管它可能在指定的時(shí)間之前被中斷)。2、調(diào)用Thread.yield():不能保障太多事情,盡管通常它會(huì)讓當(dāng)前運(yùn)行線程回到可運(yùn)行性狀態(tài),使得有相同優(yōu)先級(jí)的線程有機(jī)會(huì)執(zhí)行。3、調(diào)用join()方法:保證當(dāng)前線程停止執(zhí)行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒有存活,則當(dāng)前線程不需要停止。除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運(yùn)行狀態(tài):1、線程的run()方法完成。2、在對(duì)象上調(diào)用wait()方法(不是在線程上調(diào)用)。3、線程不能在對(duì)象上獲得鎖定,它正試圖運(yùn)行該對(duì)象的方法代碼。4、線程調(diào)度程序可以決定將當(dāng)前運(yùn)行狀態(tài)移動(dòng)到可運(yùn)行狀態(tài),以便讓另一個(gè)線程獲得運(yùn)行機(jī)會(huì),而不需要任何理由。Java線程:線程的同步與鎖一、同步問題提出線程的同步是為了防止多個(gè)線程訪問一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成的破壞。例如:兩個(gè)線程ThreadA、ThreadB都操作同一個(gè)對(duì)象Foo對(duì)象,并修改Foo對(duì)象上的數(shù)據(jù)。publicclassFoo{privateintx=100;publicintgetX(){returnx;}publicintfix(inty){x=x-y;returnx;}}publicclassMyRunnableimplementsRunnable{privateFoofoo=newFoo();publicstaticvoidmain(String[]args){MyRunnabler=newMyRunnable();Threadta=newThread(r,"Thread-A");Threadtb=newThread(r,"Thread-B");ta.start();tb.start();}publicvoidrun(){for(inti=0;i<3;i++){this.fix(30);try{Thread.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":當(dāng)前foo對(duì)象x值="+foo.getX());}}publicintfix(inty){returnfoo.fix(y);}}運(yùn)行結(jié)果:Thread-A:當(dāng)前foo對(duì)象的x值=40Thread-B:當(dāng)前foo對(duì)象的x值=40Thread-B:當(dāng)前foo對(duì)象的x值=-20Thread-A:當(dāng)前foo對(duì)象的x值=-50Thread-A:當(dāng)前foo對(duì)象的x值=-80Thread-B:當(dāng)前foo對(duì)象的x值=-80Processfinishedwithexitcode0從結(jié)果發(fā)現(xiàn),這樣的輸出值明顯是不合理的。原因是兩個(gè)線程不加控制的訪問Foo對(duì)象并修改其數(shù)據(jù)所致。如果要保持結(jié)果的合理性,只需要達(dá)到一個(gè)目的,就是將對(duì)Foo的訪問加以限制,每次只能有一個(gè)線程在訪問。這樣就能保證Foo對(duì)象中數(shù)據(jù)的合理性了。在具體的Java代碼中需要完成一下兩個(gè)操作:把競爭訪問的資源類Foo變量x標(biāo)識(shí)為private;同步哪些修改變量的代碼,使用synchronized關(guān)鍵字同步方法或代碼。二、同步和鎖定1、鎖的原理Java中每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖。當(dāng)程序運(yùn)行到非靜態(tài)的synchronized同步方法上時(shí),自動(dòng)獲得與正在執(zhí)行代碼類的當(dāng)前實(shí)例(this實(shí)例)有關(guān)的鎖。獲得一個(gè)對(duì)象的鎖也稱為獲取鎖、鎖定對(duì)象、在對(duì)象上鎖定或在對(duì)象上同步。當(dāng)程序運(yùn)行到synchronized同步方法或代碼塊時(shí)才該對(duì)象鎖才起作用。一個(gè)對(duì)象只有一個(gè)鎖。所以,如果一個(gè)線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個(gè)線程釋放(或返回)鎖。這也意味著任何其他線程都不能進(jìn)入該對(duì)象上的synchronized方法或代碼塊,直到該鎖被釋放。釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。關(guān)于鎖和同步,有一下幾個(gè)要點(diǎn):1)、只能同步方法,而不能同步變量和類;2)、每個(gè)對(duì)象只有一個(gè)鎖;當(dāng)提到同步時(shí),應(yīng)該清楚在什么上同步?也就是說,在哪個(gè)對(duì)象上同步?3)、不必同步類中所有的方法,類可以同時(shí)擁有同步和非同步方法。4)、如果兩個(gè)線程要執(zhí)行一個(gè)類中的synchronized方法,并且兩個(gè)線程使用相同的實(shí)例來調(diào)用方法,那么一次只能有一個(gè)線程能夠執(zhí)行方法,另一個(gè)需要等待,直到鎖被釋放。也就是說:如果一個(gè)線程在對(duì)象上獲得一個(gè)鎖,就沒有任何其他線程可以進(jìn)入(該對(duì)象的)類中的任何一個(gè)同步方法。5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個(gè)線程自由訪問而不受鎖的限制。6)、線程睡眠時(shí),它所持的任何鎖都不會(huì)釋放。7)、線程可以獲得多個(gè)鎖。比如,在一個(gè)對(duì)象的同步方法里面調(diào)用另外一個(gè)對(duì)象的同步方法,則獲取了兩個(gè)對(duì)象的同步鎖。8)、同步損害并發(fā)性,應(yīng)該盡可能縮小同步范圍。同步不但可以同步整個(gè)方法,還可以同步方法中一部分代碼塊。9)、在使用同步代碼塊時(shí)候,應(yīng)該指定在哪個(gè)對(duì)象上同步,也就是說要獲取哪個(gè)對(duì)象的鎖。例如:publicintfix(inty){synchronized(this){x=x-y;}returnx;}當(dāng)然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:publicsynchronizedintgetX(){returnx++;}與publicintgetX(){synchronized(this){returnx;}}效果是完全一樣的。三、靜態(tài)方法同步要同步靜態(tài)方法,需要一個(gè)用于整個(gè)類對(duì)象的鎖,這個(gè)對(duì)象是就是這個(gè)類(XXX.class)。例如:publicstaticsynchronizedintsetName(Stringname){X=name;}等價(jià)于publicstaticintsetName(Stringname){synchronized(Xxx.class){X=name;}}四、如果線程不能不能獲得鎖會(huì)怎么樣如果線程試圖進(jìn)入同步方法,而其鎖已經(jīng)被占用,則線程在該對(duì)象上被阻塞。實(shí)質(zhì)上,線程進(jìn)入該對(duì)象的的一種池中,必須在哪里等待,直到其鎖被釋放,該線程再次變?yōu)榭蛇\(yùn)行或運(yùn)行為止。當(dāng)考慮阻塞時(shí),一定要注意哪個(gè)對(duì)象正被用于鎖定:1、調(diào)用同一個(gè)對(duì)象中非靜態(tài)同步方法的線程將彼此阻塞。如果是不同對(duì)象,則每個(gè)線程有自己的對(duì)象的鎖,線程間彼此互不干預(yù)。2、調(diào)用同一個(gè)類中的靜態(tài)同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對(duì)象上。3、靜態(tài)同步方法和非靜態(tài)同步方法將永遠(yuǎn)不會(huì)彼此阻塞,因?yàn)殪o態(tài)方法鎖定在Class對(duì)象上,非靜態(tài)方法鎖定在該類的對(duì)象上。4、對(duì)于同步代碼塊,要看清楚什么對(duì)象已經(jīng)用于鎖定(synchronized后面括號(hào)的內(nèi)容)。在同一個(gè)對(duì)象上進(jìn)行同步的線程將彼此阻塞,在不同對(duì)象上鎖定的線程將永遠(yuǎn)不會(huì)彼此阻塞。五、何時(shí)需要同步在多個(gè)線程同時(shí)訪問互斥(可交換)數(shù)據(jù)時(shí),應(yīng)該同步以保護(hù)數(shù)據(jù),確保兩個(gè)線程不會(huì)同時(shí)修改更改它。對(duì)于非靜態(tài)字段中可更改的數(shù)據(jù),通常使用非靜態(tài)方法訪問。對(duì)于靜態(tài)字段中可更改的數(shù)據(jù),通常使用靜態(tài)方法訪問。如果需要在非靜態(tài)方法中使用靜態(tài)字段,或者在靜態(tài)字段中調(diào)用非靜態(tài)方法,問題將變得非常復(fù)雜。已經(jīng)超出SJCP考試范圍了。六、線程安全類當(dāng)一個(gè)類已經(jīng)很好的同步以保護(hù)它的數(shù)據(jù)時(shí),這個(gè)類就稱為“線程安全的”。即使是線程安全類,也應(yīng)該特別小心,因?yàn)椴僮鞯木€程之間仍然不一定安全。舉個(gè)形象的例子,比如一個(gè)集合是線程安全的,有兩個(gè)線程在操作同一個(gè)集合對(duì)象,當(dāng)?shù)谝粋€(gè)線程查詢集合非空后,刪除集合中所有元素的時(shí)候。第二個(gè)線程也來執(zhí)行與第一個(gè)線程相同的操作,也許在第一個(gè)線程查詢后,第二個(gè)線程也查詢出集合非空,但是當(dāng)?shù)谝粋€(gè)執(zhí)行清除后,第二個(gè)再執(zhí)行刪除顯然是不對(duì)的,因?yàn)榇藭r(shí)集合已經(jīng)為空了。看個(gè)代碼:publicclassNameList{privateListnameList=Collections.synchronizedList(newLinkedList());publicvoidadd(Stringname){nameList.add(name);}publicStringremoveFirst(){if(nameList.size()>0){return(String)nameList.remove(0);}else{returnnull;}}}publicclassTest{publicstaticvoidmain(String[]args){finalNameListnl=newNameList();nl.add("aaa");classNameDropperextendsThread{publicvoidrun(){Stringname=nl.removeFirst();System.out.println(name);}}Threadt1=newNameDropper();Threadt2=newNameDropper();t1.start();t2.start();}}雖然集合對(duì)象privateListnameList=Collections.synchronizedList(newLinkedList());是同步的,但是程序還不是線程安全的。出現(xiàn)這種事件的原因是,上例中一個(gè)線程操作列表過程中無法阻止另外一個(gè)線程對(duì)列表的其他操作。解決上面問題的辦法是,在操作集合對(duì)象的NameList上面做一個(gè)同步。改寫后的代碼如下:publicclassNameList{privateListnameList=Collections.synchronizedList(newLinkedList());publicsynchronizedvoidadd(Stringname){nameList.add(name);}publicsynchronizedStringremoveFirst(){if(nameList.size()>0){return(String)nameList.remove(0);}else{returnnull;}}}這樣,當(dāng)一個(gè)線程訪問其中一個(gè)同步方法時(shí),其他線程只有等待。七、線程死鎖死鎖對(duì)Java程序來說,是很復(fù)雜的,也很難發(fā)現(xiàn)問題。當(dāng)兩個(gè)線程被阻塞,每個(gè)線程在等待另一個(gè)線程時(shí)就發(fā)生死鎖。還是看一個(gè)比較直觀的死鎖例子:publicclassDeadlockRisk{privatestaticclassResource{publicintvalue;}privateResourceresourceA=newResource();privateResourceresourceB=newResource();publicintread(){synchronized(resourceA){synchronized(resourceB){returnresourceB.value+resourceA.value;}}}publicvoidwrite(inta,intb){synchronized(resourceB){synchronized(resourceA){resourceA.value=a;resourceB.value=b;}}}}假設(shè)read()方法由一個(gè)線程啟動(dòng),write()方法由另外一個(gè)線程啟動(dòng)。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,兩者都堅(jiān)持等待的話就出現(xiàn)死鎖。實(shí)際上,上面這個(gè)例子發(fā)生死鎖的概率很小。因?yàn)樵诖a內(nèi)的某個(gè)點(diǎn),CPU必須從讀線程切換到寫線程,所以,死鎖基本上不能發(fā)生。但是,無論代碼中發(fā)生死鎖的概率有多小,一旦發(fā)生死鎖,程序就死掉。有一些設(shè)計(jì)方法能幫助避免死鎖,包括始終按照預(yù)定義的順序獲取鎖這一策略。已經(jīng)超出SCJP的考試范圍。八、線程同步小結(jié)1、線程同步的目的是為了保護(hù)多個(gè)線程反問一個(gè)資源時(shí)對(duì)資源的破壞。2、線程同步方法是通過鎖來實(shí)現(xiàn),每個(gè)對(duì)象都有切僅有一個(gè)鎖,這個(gè)鎖與一個(gè)特定的對(duì)象關(guān)聯(lián),線程一旦獲取了對(duì)象鎖,其他訪問該對(duì)象的線程就無法再訪問該對(duì)象的其他同步方法。3、對(duì)于靜態(tài)同步方法,鎖是針對(duì)這個(gè)類的,鎖對(duì)象是該類的Class對(duì)象。靜態(tài)和非靜態(tài)方法的鎖互不干預(yù)。一個(gè)線程獲得鎖,當(dāng)在一個(gè)同步方法中訪問另外對(duì)象上的同步方法時(shí),會(huì)獲取這兩個(gè)對(duì)象鎖。4、對(duì)于同步,要時(shí)刻清醒在哪個(gè)對(duì)象上同步,這是關(guān)鍵。5、編寫線程安全的類,需要時(shí)刻注意對(duì)多個(gè)線程競爭訪問資源的邏輯和安全做出正確的判斷,對(duì)“原子”操作做出分析,并保證原子操作期間別的線程無法訪問競爭資源。6、當(dāng)多個(gè)線程等待一個(gè)對(duì)象鎖時(shí),沒有獲取到鎖的線程將發(fā)生阻塞。7、死鎖是線程間相互等待鎖鎖造成的,在實(shí)際中發(fā)生的概率非常的小。真讓你寫個(gè)死鎖程序,不一定好使,呵呵。但是,一旦程序發(fā)生死鎖,程序?qū)⑺赖?。Java線程:線程的交互線程交互是比較復(fù)雜的問題,SCJP要求不很基礎(chǔ):給定一個(gè)場景,編寫代碼來恰當(dāng)使用等待、通知和通知所有線程。一、線程交互的基礎(chǔ)知識(shí)SCJP所要求的線程交互知識(shí)點(diǎn)需要從java.lang.Object的類的三個(gè)方法來學(xué)習(xí):voidnotify()喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。voidnotifyAll()喚醒在此對(duì)象監(jiān)視器上等待的所有線程。voidwait()導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法。當(dāng)然,wait()還有另外兩個(gè)重載方法:voidwait(longtimeout)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法,或者超過指定的時(shí)間量。voidwait(longtimeout,intnanos)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量。以上這些方法是幫助線程傳遞線程關(guān)心的時(shí)間狀態(tài)。關(guān)于等待/通知,要記住的關(guān)鍵點(diǎn)是:必須從同步環(huán)境內(nèi)調(diào)用wait()、notify()、notifyAll()方法。線程不能調(diào)用對(duì)象上等待或通知的方法,除非它擁有那個(gè)對(duì)象的鎖。wait()、notify()、notifyAll()都是Object的實(shí)例方法。與每個(gè)對(duì)象具有鎖一樣,每個(gè)對(duì)象可以有一個(gè)線程列表,他們等待來自該信號(hào)(通知)。線程通過執(zhí)行對(duì)象上的wait()方法獲得這個(gè)等待列表。從那時(shí)候起,它不再執(zhí)行任何其他指令,直到調(diào)用對(duì)象的notify()方法為止。如果多個(gè)線程在同一個(gè)對(duì)象上等待,則將只選擇一個(gè)線程(不保證以何種順序)繼續(xù)執(zhí)行。如果沒有線程等待,則不采取任何特殊操作。下面看個(gè)例子就明白了:/**計(jì)算輸出其他線程鎖計(jì)算的數(shù)據(jù)@authorleizhimin2008-9-1513:20:38*/publicclassThreadA{publicstaticvoidmain(String[]args){ThreadBb=newThreadB();//啟動(dòng)計(jì)算線程b.start();//線程A擁有b對(duì)象上的鎖。線程為了調(diào)用wait()或notify()方法,該線程必須是那個(gè)對(duì)象鎖的擁有者synchronized(b){try{System.out.println("等待對(duì)象b完成計(jì)算。。。");//當(dāng)前線程A等待b.wait();}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("b對(duì)象計(jì)算的總和是:"+b.total);}}}/**計(jì)算1+2+3...+100的和@authorleizhimin2008-9-1513:20:49*/publicclassThreadBextendsThread{inttotal;publicvoidrun(){synchronized(this){for(inti=0;i<101;i++){total+=i;}//(完成計(jì)算了)喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程,在本例中線程A被喚醒notify();}}}等待對(duì)象b完成計(jì)算。。。對(duì)象計(jì)算的總和是:5050Processfinishedwithexitcode0千萬注意:當(dāng)在對(duì)象上調(diào)用wait()方法時(shí),執(zhí)行該代碼的線程立即放棄它在對(duì)象上的鎖。然而調(diào)用notify()時(shí),并不意味著這時(shí)線程會(huì)放棄其鎖。如果線程榮然在完成同步代碼,則線程在移出之前不會(huì)放棄鎖。因此,只要調(diào)用notify()并不意味著這時(shí)該鎖變得可用。二、多個(gè)線程在等待一個(gè)對(duì)象鎖時(shí)候使用notifyAll()在多數(shù)情況下,最好通知等待某個(gè)對(duì)象的所有線程。如果這樣做,可以在對(duì)象上使用notifyAll()讓所有在此對(duì)象上等待的線程沖出等待區(qū),返回到可運(yùn)行狀態(tài)。下面給個(gè)例子:/**計(jì)算線程@authorleizhimin2008-9-2011:15:46*/publicclassCalculatorextendsThread{inttotal;publicvoidrun(){synchronized(this){for(inti=0;i<101;i++){total+=i;}}//通知所有在此對(duì)象上等待的線程notifyAll();}}/**獲取計(jì)算結(jié)果并輸出@authorleizhimin2008-9-2011:15:22*/publicclassReaderResultextendsThread{Calculatorc;publicReaderResult(Calculatorc){this.c=c;}publicvoidrun(){synchronized(c){try{System.out.println(Thread.currentThread()+"等待計(jì)算結(jié)果。。。");c.wait();}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread()+"計(jì)算結(jié)果為:"+c.total);}}publicstaticvoidmain(String[]args){Calculatorcalculator=newCalculator();//啟動(dòng)三個(gè)線程,分別獲取計(jì)算結(jié)果newReaderResult(calculator).start();newReaderResult(calculator).start();newReaderResult(calculator).start();//啟動(dòng)計(jì)算線程calculator.start();}}運(yùn)行結(jié)果:Thread[Thread-1,5,main]等待計(jì)算結(jié)果。。。Thread[Thread-2,5,main]等待計(jì)算結(jié)果。。。Thread[Thread-3,5,main]等待計(jì)算結(jié)果。。。Exceptioninthread"Thread-0"java.lang.IllegalMonitorStateException:currentthreadnotowneratjava.lang.Object.notifyAll(NativeMethod)atthreadtest.Calculator.run(Calculator.java:18)Thread[Thread-1,5,main]計(jì)算結(jié)果為:5050Thread[Thread-2,5,main]計(jì)算結(jié)果為:5050Thread[Thread-3,5,main]計(jì)算結(jié)果為:5050Processfinishedwithexitcode0運(yùn)行結(jié)果表明,程序中有異常,并且多次運(yùn)行結(jié)果可能有多種輸出結(jié)果。這就是說明,這個(gè)多線程的交互程序還存在問題。究竟是出了什么問題,需要深入的分析和思考,下面將做具體分析。實(shí)際上,上面這個(gè)代碼中,我們期望的是讀取結(jié)果的線程在計(jì)算線程調(diào)用notifyAll()之前等待即可。但是,如果計(jì)算線程先執(zhí)行,并在讀取結(jié)果線程等待之前調(diào)用了notify()方法,那么又會(huì)發(fā)生什么呢?這種情況是可能發(fā)生的。因?yàn)闊o法保證線程的不同部分將按照什么順序來執(zhí)行。幸運(yùn)的是當(dāng)讀取線程運(yùn)行時(shí),它只能馬上進(jìn)入等待狀態(tài)----它沒有做任何事情來檢查等待的事件是否已經(jīng)發(fā)生。----因此,如果計(jì)算線程已經(jīng)調(diào)用了notifyAll()方法,那么它就不會(huì)再次調(diào)用notifyAll(),----并且等待的讀取線程將永遠(yuǎn)保持等待。這當(dāng)然是開發(fā)者所不愿意看到的問題。因此,當(dāng)?shù)却氖录l(fā)生時(shí),需要能夠檢查notifyAll()通知事件是否已經(jīng)發(fā)生。通常,解決上面問題的最佳方式是將(博主到這句話就太監(jiān)了,先留著,以后補(bǔ)上…)Java線程:線程的調(diào)度-休眠Java線程調(diào)度是Java多線程的核心,只有良好的調(diào)度,才能充分發(fā)揮系統(tǒng)的性能,提高程序的執(zhí)行效率。這里要明確的一點(diǎn),不管程序員怎么編寫調(diào)度,只能最大限度的影響線程執(zhí)行的次序,而不能做到精準(zhǔn)控制。線程休眠的目的是使線程讓出CPU的最簡單的做法之一,線程休眠時(shí)候,會(huì)將CPU資源交給其他線程,以便能輪換執(zhí)行,當(dāng)休眠一定時(shí)間后,線程會(huì)蘇醒,進(jìn)入準(zhǔn)備狀態(tài)等待執(zhí)行。線程休眠的方法是Thread.sleep(longmillis)和Thread.sleep(longmillis,intnanos),均為靜態(tài)方法,那調(diào)用sleep休眠的哪個(gè)線程呢?簡單說,哪個(gè)線程調(diào)用sleep,就休眠哪個(gè)線程。/**Java線程:線程的調(diào)度-休眠@authorleizhimin2009-11-49:02:40*/publicclassTest{publicstaticvoidmain(String[]args){Threadt1=newMyThread1();Threadt2=newThread(newMyRunnable());t1.start();t2.start();}}classMyThread1extendsThread{publicvoidrun(){for(inti=0;i<3;i++){System.out.println("線程1第"+i+"次執(zhí)行!");try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}}}}classMyRunnableimplementsRunnable{publicvoidrun(){for(inti=0;i<3;i++){System.out.println("線程2第"+i+"次執(zhí)行!");try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}}}}線程2第0次執(zhí)行!線程1第0次執(zhí)行!線程1第1次執(zhí)行!線程2第1次執(zhí)行!線程1第2次執(zhí)行!線程2第2次執(zhí)行!Processfinishedwithexitcode0從上面的結(jié)果輸出可以看出,無法精準(zhǔn)保證線程執(zhí)行次序。Java線程:線程的調(diào)度-優(yōu)先級(jí)與線程休眠類似,線程的優(yōu)先級(jí)仍然無法保障線程的執(zhí)行次序。只不過,優(yōu)先級(jí)高的線程獲取CPU資源的概率較大,優(yōu)先級(jí)低的并非沒機(jī)會(huì)執(zhí)行。線程的優(yōu)先級(jí)用1-10之間的整數(shù)表示,數(shù)值越大優(yōu)先級(jí)越高,默認(rèn)的優(yōu)先級(jí)為5。在一個(gè)線程中開啟另外一個(gè)新線程,則新開線程稱為該線程的子線程,子線程初始優(yōu)先級(jí)與父線程相同。/**Java線程:線程的調(diào)度-優(yōu)先級(jí)*/publicclassTest{publicstaticvoidmain(String[]args){Threadt1=newMyThread1();Threadt2=newThread(newMyRunnable());t1.setPriority(10);t2.setPriority(1);t2.start();t1.start();}}classMyThread1extendsThread{publicvoidrun(){for(inti=0;i<10;i++){System.out.println("線程1第"+i+"次執(zhí)行!");try{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}classMyRunnableimplementsRunnable{publicvoidrun(){for(inti=0;i<10;i++){System.out.println("線程2第"+i+"次執(zhí)行!");try{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}線程1第0次執(zhí)行!線程2第0次執(zhí)行!線程2第1次執(zhí)行!線程1第1次執(zhí)行!線程2第2次執(zhí)行!線程1第2次執(zhí)行!線程1第3次執(zhí)行!線程2第3次執(zhí)行!線程2第4次執(zhí)行!線程1第4次執(zhí)行!線程1第5次執(zhí)行!線程2第5次執(zhí)行!線程1第6次執(zhí)行!線程2第6次執(zhí)行!線程1第7次執(zhí)行!線程2第7次執(zhí)行!線程1第8次執(zhí)行!線程2第8次執(zhí)行!線程1第9次執(zhí)行!線程2第9次執(zhí)行!Processfinishedwithexitcode0Java線程:線程的調(diào)度-讓步線程的讓步含義就是使當(dāng)前運(yùn)行著線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態(tài)回到可運(yùn)行狀態(tài)。線程的讓步使用Thread.yield()方法,yield()為靜態(tài)方法,功能是暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。/**Java線程:線程的調(diào)度-讓步*/publicclassTest{publicstaticvoidmain(String[]args){Threadt1=newMyThread1();Threadt2=newThread(newMyRunnable());t2.start();t1.start();}}classMyThread1extendsThread{publicvoidrun(){for(inti=0;i<10;i++){System.out.println("線程1第"+i+"次執(zhí)行!");}}}classMyRunnableimplementsRunnable{publicvoidrun(){for(inti=0;i<10;i++){System.out.println("線程2第"+i+"次執(zhí)行!");Thread.yield();}}}線程2第0次執(zhí)行!線程2第1次執(zhí)行!線程2第2次執(zhí)行!線程2第3次執(zhí)行!線程1第0次執(zhí)行!……………Java線程:線程的調(diào)度-合并線程的合并的含義就是將幾個(gè)并行線程的線程合并為一個(gè)單線程執(zhí)行,應(yīng)用場景是當(dāng)一個(gè)線程必須等待另一個(gè)線程執(zhí)行完畢才能執(zhí)行時(shí)可以使用join方法。join為非靜態(tài)方法,定義如下:voidjoin()等待該線程終止。voidjoin(longmillis)等待該線程終止的時(shí)間最長為millis毫秒。voidjoin(longmillis,intnanos)等待該線程終止的時(shí)間最長為millis毫秒+nanos納秒。/**Java線程:線程的調(diào)度-合并*/publicclassTest{publicstaticvoidmain(String[]args){Threadt1=newMyThread1();t1.start();for(inti=0;i<20;i++){System.out.println("主線程第"+i+"次執(zhí)行!");if(i>2)try{//t1線程合并到主線程中,主線程停止執(zhí)行過程,轉(zhuǎn)而執(zhí)行t1線程,直到t1執(zhí)行完畢后繼續(xù)。t1.join();}catch(InterruptedExceptione){e.printStackTrace();}}}}classMyThread1extendsThread{publicvoidrun(){for(inti=0;i<10;i++){System.out.println("線程1第"+i+"次執(zhí)行!");}}}主線程第0次執(zhí)行!主線程第1次執(zhí)行!主線程第2次執(zhí)行!…………….Java線程:線程的調(diào)度-守護(hù)線程守護(hù)線程與普通線程寫法上基本么啥區(qū)別,調(diào)用線程對(duì)象的方法setDaemon(true),則可以將其設(shè)置為守護(hù)線程。守護(hù)線程使用的情況較少,但并非無用,舉例來說,JVM的垃圾回收、內(nèi)存管理等線程都是守護(hù)線程。還有就是在做數(shù)據(jù)庫應(yīng)用時(shí)候,使用的數(shù)據(jù)庫連接池,連接池本身也包含著很多后臺(tái)線程,監(jiān)控連接個(gè)數(shù)、超時(shí)時(shí)間、狀態(tài)等等。setDaemon方法的詳細(xì)說明:publicfinalvoidsetDaemon(booleanon)將該線程標(biāo)記為守護(hù)線程或用戶線程。當(dāng)正在運(yùn)行的線程都是守護(hù)線程時(shí),Java虛擬機(jī)退出。該方法必須在啟動(dòng)線程前調(diào)用。該方法首先調(diào)用該線程的checkAccess方法,且不帶任何參數(shù)。這可能拋出SecurityException(在當(dāng)前線程中)。參數(shù):on-如果為true,則將該線程標(biāo)記為守護(hù)線程。拋出:IllegalThreadStateException-如果該線程處于活動(dòng)狀態(tài)。SecurityException-如果當(dāng)前線程無法修改該線程。另請(qǐng)參見:isDaemon(),checkAccess()/**Java線程:線程的調(diào)度-守護(hù)線程*/publicclassTest{publicstaticvoidmain(String[]args){Threadt1=newMyCommon();Threadt2=newThread(newMyDaemon());t2.setDaemon(true);//設(shè)置為守護(hù)線程t2.start();t1.start();}}classMyCommonextendsThread{publicvoidrun(){for(inti=0;i<5;i++){System.out.println("線程1第"+i+"次執(zhí)行!");try{Thread.sleep(7);}catch(InterruptedExceptione){e.printStackTrace();}}}}classMyDaemonimplementsRunnable{publicvoidrun(){for(longi=0;i<9999999L;i++){System.out.println("后臺(tái)線程第"+i+"次執(zhí)行!");try{Thread.sleep(7);}catch(InterruptedExceptione){e.printStackTrace();}}}}后臺(tái)線程第0次執(zhí)行!線程1第0次執(zhí)行!線程1第1次執(zhí)行!后臺(tái)線程第1次執(zhí)行!后臺(tái)線程第2次執(zhí)行!線程1第2次執(zhí)行!線程1第3次執(zhí)行!后臺(tái)線程第3次執(zhí)行!線程1第4次執(zhí)行!后臺(tái)線程第4次執(zhí)行!后臺(tái)線程第5次執(zhí)行!后臺(tái)線程第6次執(zhí)行!后臺(tái)線程第7次執(zhí)行!Processfinishedwithexitcode0從上面的執(zhí)行結(jié)果可以看出:前臺(tái)線程是保證執(zhí)行完畢的,后臺(tái)線程還沒有執(zhí)行完畢就退出了。實(shí)際上:JRE判斷程序是否執(zhí)行結(jié)束的標(biāo)準(zhǔn)是所有的前臺(tái)執(zhí)線程行完畢了,而不管后臺(tái)線程的狀態(tài),因此,在使用后臺(tái)線程時(shí)候一定要注意這個(gè)問題。Java線程:線程的同步-同步方法線程的同步是保證多線程安全訪問競爭資源的一種手段。線程的同步是Java多線程編程的難點(diǎn),往往開發(fā)者搞不清楚什么是競爭資源、什么時(shí)候需要考慮同步,怎么同步等等問題,當(dāng)然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時(shí)改動(dòng)的問題?在本文之前,請(qǐng)參閱《Java線程:線程的同步與鎖》,本文是在此基礎(chǔ)上所寫的。對(duì)于同步,在具體的Java代碼中需要完成一下兩個(gè)操作:把競爭訪問的資源標(biāo)識(shí)為private;同步哪些修改變量的代碼,使用synchronized關(guān)鍵字同步方法或代碼。當(dāng)然這不是唯一控制并發(fā)安全的途徑。synchronized關(guān)鍵字使用說明synchronized只能標(biāo)記非抽象的方法,不能標(biāo)識(shí)成員變量。為了演示同步方法的使用,構(gòu)建了一個(gè)信用卡賬戶,起初信用額為100w,然后模擬透支、存款等多個(gè)操作。顯然銀行賬戶User對(duì)象是個(gè)競爭資源,而多個(gè)并發(fā)操作的是賬戶方法oper(intx),當(dāng)然應(yīng)該在此方法上加上同步,并將賬戶的余額設(shè)為私有變量,禁止直接訪問。/**Java線程:線程的同步*/publicclassTest{publicstaticvoidmain(String[]args){Useru=newUser("張三",100);MyThreadt1=newMyThread("線程A",u,20);MyThreadt2=newMyThread("線程B",u,-60);MyThreadt3=newMyThread("線程C",u,-80);MyThreadt4=newMyThread("線程D",u,-30);MyThreadt5=newMyThread("線程E",u,32);MyThreadt6=newMyThread("線程F",u,21);t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}}classMyThreadextendsThread{privateUseru;privateinty=0;MyThread(Stringname,Useru,inty){super(name);this.u=u;this.y=y;}publicvoidrun(){u.oper(y);}}classUser{privateStringcode;privateintcash;User(Stringcode,intcash){this.code=code;this.cash=cash;}publicStringgetCode(){returncode;}publicvoidsetCode(Stringcode){this.code=code;}/**業(yè)務(wù)方法@paramx添加x萬元*/publicsynchronizedvoidoper(intx){try{Thread.sleep(10L);this.cash+=x;System.out.println(Thread.currentThread().getName()+"運(yùn)行結(jié)束,增加“"+x+"”,當(dāng)前用戶賬戶余額為:"+cash);Thread.sleep(10L);}catch(InterruptedExceptione){e.printStackTrace();}}@OverridepublicStringtoString(){return"User{"+"code='"+code+'\''+",cash="+cash+'}';}}輸出結(jié)果:線程A運(yùn)行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:120線程F運(yùn)行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:141線程E運(yùn)行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:173線程C運(yùn)行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:93線程B運(yùn)行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:33線程D運(yùn)行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:3Processfinishedwithexitcode0反面教材,不同步的情況,也就是去掉oper(intx)方法的synchronized修飾符,然后運(yùn)行程序,結(jié)果如下:線程A運(yùn)行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:61線程D運(yùn)行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:63線程B運(yùn)行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:3線程F運(yùn)行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:61線程E運(yùn)行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:93線程C運(yùn)行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:61Processfinishedwithexitcode0很顯然,上面的結(jié)果是錯(cuò)誤的,導(dǎo)致錯(cuò)誤的原因是多個(gè)線程并發(fā)訪問了競爭資源u,并對(duì)u的屬性做了改動(dòng)??梢娡降闹匾?。注意:通過前文可知,線程退出同步方法時(shí)將釋放掉方法所屬對(duì)象的鎖,但還應(yīng)該注意的是,同步方法中還可以使用特定的方法對(duì)線程進(jìn)行調(diào)度。這些方法來自于java.lang.Object類。voidnotify()喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。voidnotifyAll()喚醒在此對(duì)象監(jiān)視器上等待的所有線程。voidwait()導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法。voidwait(longtimeout)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法,或者超過指定的時(shí)間量。voidwait(longtimeout,intnanos)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量。結(jié)合以上方法,處理多線程同步與互斥問題非常重要,著名的生產(chǎn)者-消費(fèi)者例子就是一個(gè)經(jīng)典的例子,任何語言多線程必學(xué)的例子。Java線程:線程的同步-同步塊對(duì)于同步,除了同步方法外,還可以使用同步代碼塊,有時(shí)候同步代碼塊會(huì)帶來比同步方法更好的效果。追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時(shí)候保證同一時(shí)刻只能一個(gè)線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能。在上個(gè)例子的基礎(chǔ)上,對(duì)oper方法做了改動(dòng),由同步方法改為同步代碼塊模式,程序的執(zhí)行邏輯并沒有問題。/**Java線程:線程的同步-同步代碼塊@authorleizhimin2009-11-411:23:32*/publicclassTest{publicstaticvoidmain(String[]args){Useru=newUser("張三",100);MyThreadt1=newMyThread("線程A",u,20);MyThreadt2=newMyThread("線程B",u,-60);MyThreadt3=newMyThread("線程C",u,-80);MyThreadt4=newMyThread("線程D",u,-30);MyThreadt5=newMyThread("線程E",u,32);MyThreadt6=newMyThread("線程F",u,21);t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}}classMyThreadextendsThread{privateUseru;privateinty=0;MyThread(Stringname,Useru,inty){super(name);this.u=u;this.y=y;}publicvoidrun(){u.oper(y);}}classUser{privateStringcode;privateintcash;User(Stringcode,intcash){this.code=code;this.cash=cash;}publicStringgetCode(){returncode;}publicvoidsetCode(Stringcode){this.code=code;}/**業(yè)務(wù)方法@paramx添加x萬元*/publicvoidoper(intx){try{Thread.sleep(10L);synchronized(this){this.cash+=x;System.out.println(Thread.currentThread().getName()+"運(yùn)行結(jié)束,增加“"+x+"”,當(dāng)前用戶賬戶余額為:"+cash);}Thread.sleep(10L);}catch(InterruptedExceptione){e.printStackTrace();}}@OverridepublicStringtoString(){return"User{"+"code='"+code+'\''+",cash="+cash+'}';}}線程E運(yùn)行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:132線程B運(yùn)行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:72線程D運(yùn)行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:42線程F運(yùn)行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:63線程C運(yùn)行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:-17線程A運(yùn)行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:3Processfinishedwithexitcode0注意:在使用synchronized關(guān)鍵字時(shí)候,應(yīng)該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因?yàn)閟ynchronized程序塊占有著對(duì)象鎖,你休息那么其他的線程只能一邊等著你醒來執(zhí)行完了才能執(zhí)行。不但嚴(yán)重影響效率,也不合邏輯。同樣,在同步程序塊內(nèi)調(diào)用yeild方法讓出CPU資源也沒有意義,因?yàn)槟阏加弥i,其他互斥線程還是無法訪問同步程序塊。當(dāng)然與同步程序塊無關(guān)的線程可以獲得更多的執(zhí)行時(shí)間。Java線程:并發(fā)協(xié)作-生產(chǎn)者消費(fèi)者模型對(duì)于多線程程序來說,不管任何編程語言,生產(chǎn)者和消費(fèi)者模型都是最經(jīng)典的。就像學(xué)習(xí)每一門編程語言一樣,HelloWorld!都是最經(jīng)典的例子。實(shí)際上,準(zhǔn)確說應(yīng)該是“生產(chǎn)者-消費(fèi)者-倉儲(chǔ)”模型,離開了倉儲(chǔ),生產(chǎn)者消費(fèi)者模型就顯得沒有說服力了。對(duì)于此模型,應(yīng)該明確一下幾點(diǎn):1、生產(chǎn)者僅僅在倉儲(chǔ)未滿時(shí)候生產(chǎn),倉滿則停止生產(chǎn)。2、消費(fèi)者僅僅在倉儲(chǔ)有產(chǎn)品時(shí)候才能消費(fèi),倉空則等待。3、當(dāng)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- SB/T 11235-2023人像攝影服務(wù)機(jī)構(gòu)誠信評(píng)價(jià)規(guī)范
- 2025年軍隊(duì)文職人員招聘之軍隊(duì)文職管理學(xué)與服務(wù)全真模擬考試試卷A卷含答案
- 2025年軍隊(duì)文職人員招聘之軍隊(duì)文職管理學(xué)提升訓(xùn)練試卷B卷附答案
- 新泰數(shù)學(xué)初一試題及答案
- 安全防火知識(shí)培訓(xùn)課件
- 2025年黨史競賽知識(shí)題庫50題及答案
- 監(jiān)理基礎(chǔ)知識(shí)培訓(xùn)課件
- 人工智能醫(yī)療輔助系統(tǒng)應(yīng)用及操作指南
- 唐宋八大家之一王安石介紹與作品欣賞教案
- 公司股份制改革法律文件匯編手冊
- 一年級(jí)上冊期末班語文質(zhì)量分析(劉玉華)
- 劍橋英語一級(jí)詞匯表
- 馬鞍山博望區(qū)新城區(qū)控制性詳細(xì)規(guī)劃的知識(shí)
- 種植義齒修復(fù)
- QPQ新工藝新技術(shù)研發(fā)、推廣、加工及QPQ金屬表面處理
- 墓碑供貨方案及服務(wù)保障措施
- ACLS-PC-SA課前自我測試試題及答案
- 第十四章磨削及砂輪課件
- 水泥企業(yè)化驗(yàn)室控制組試題(庫)
- 肇慶市勞動(dòng)合同
- 電力施工安全技術(shù)交底記錄表
評(píng)論
0/150
提交評(píng)論