版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第5章多線程5.1線程的概念5.2多線程的實(shí)現(xiàn)方法5.3采用多線程實(shí)現(xiàn)動(dòng)畫(huà)效果5.4線程的同步與死鎖 5.1線?程?的?概?念
5.1.1線程與多線程
線程是指程序中順序執(zhí)行的一個(gè)指令序列,多線程允許在程序中并發(fā)執(zhí)行多個(gè)指令序列,且彼此間互相獨(dú)立。
多線程允許將程序任務(wù)分成幾個(gè)并行的子任務(wù),以提高系統(tǒng)的運(yùn)行效率。例如,在網(wǎng)絡(luò)編程中,很多功能是可以并發(fā)執(zhí)行的。如果需要從ftp文件服務(wù)器下載文件,由于網(wǎng)絡(luò)傳輸速度較慢,客戶(hù)端提出請(qǐng)求后,須等待服務(wù)器響應(yīng),此時(shí)客戶(hù)端處于閑置等待狀態(tài)。如果用兩個(gè)獨(dú)立的線程去完成該功能,當(dāng)一個(gè)線程處于等待狀態(tài)時(shí),另一個(gè)線程可以建立連接,請(qǐng)求另一部分?jǐn)?shù)據(jù)。采用多線程可以充分利用網(wǎng)絡(luò)帶寬,提高文件下載的速度。在圖形用戶(hù)界面的程序中,主程序不斷檢測(cè)發(fā)生的事件,根據(jù)事件的不同種類(lèi)調(diào)用事件的響應(yīng)程序。如果采用單線程,只有當(dāng)事件響應(yīng)程序執(zhí)行完畢,主程序才能繼續(xù)處理其他的事件。
多個(gè)線程的執(zhí)行是并發(fā)的,也就是在邏輯上“同時(shí)”,而不管是否是物理上的“同時(shí)”。如果系統(tǒng)只有一個(gè)CPU,那么真正的“同時(shí)”是不可能的,但是由于CPU的速度非???,用戶(hù)感覺(jué)不到其中的區(qū)別,只需要設(shè)想各個(gè)線程是同時(shí)執(zhí)行即可。
多線程和傳統(tǒng)的單線程在程序設(shè)計(jì)上最大的區(qū)別在于:由于各個(gè)線程的控制流彼此獨(dú)立,使得各個(gè)線程之間代碼執(zhí)行的順序不確定,由此帶來(lái)線程調(diào)度、同步等問(wèn)題。5.1.2進(jìn)程與線程
進(jìn)程是程序的一次動(dòng)態(tài)執(zhí)行過(guò)程,它對(duì)應(yīng)了從代碼加載、執(zhí)行,到執(zhí)行完畢的一個(gè)完整過(guò)程,這個(gè)過(guò)程也是進(jìn)程本身從產(chǎn)生、發(fā)展到消亡的過(guò)程。同一個(gè)程序可以被加載到系統(tǒng)的不同內(nèi)存區(qū)域分別執(zhí)行,形成不同的進(jìn)程。
線程是比進(jìn)程更小的執(zhí)行單位。一個(gè)進(jìn)程在其執(zhí)行過(guò)程中可以產(chǎn)生多個(gè)線程,每個(gè)線程也有它自身的產(chǎn)生、存在和消亡過(guò)程,是一個(gè)動(dòng)態(tài)的概念。進(jìn)程之間的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨(dú)立的,同一進(jìn)程的多個(gè)線程共享一塊內(nèi)存空間和一組系統(tǒng)資源,有可能互相影響。線程切換時(shí)保存其內(nèi)部狀態(tài)的數(shù)據(jù)較少,切換的負(fù)擔(dān)比進(jìn)程切換要小。5.1.3線程的優(yōu)先級(jí)與類(lèi)別
每一個(gè)線程都有一個(gè)優(yōu)先級(jí),Java將線程的優(yōu)先級(jí)分為10個(gè)等級(jí),分別用1~10之間的數(shù)字表示。數(shù)字越大表明線程的優(yōu)先級(jí)越高,缺省情況下的優(yōu)先級(jí)為5。Java語(yǔ)言在線程類(lèi)Thread中定義了表示線程最低、最高和普通優(yōu)先級(jí)的常量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的優(yōu)先級(jí)等級(jí)分別為1、10和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í)行。具有相同優(yōu)先級(jí)的所有線程采用輪轉(zhuǎn)的方式來(lái)共同分配CPU時(shí)間。
Java語(yǔ)言中的線程分為兩類(lèi),即用戶(hù)線程和守護(hù)(Daemon)線程。守護(hù)線程具有最低的優(yōu)先級(jí),用于為系統(tǒng)中的其他對(duì)象和線程提供服務(wù)。典型的守護(hù)線程的例子是Java虛擬機(jī)中的系統(tǒng)資源自動(dòng)回收線程,它始終在低級(jí)別的狀態(tài)中運(yùn)行,用于實(shí)時(shí)監(jiān)控和管理系統(tǒng)中的可回收資源。
Java程序運(yùn)行到所有用戶(hù)線程終止,然后終止所有的守護(hù)線程。對(duì)一個(gè)Java應(yīng)用程序,main方法運(yùn)行結(jié)束以后,如果另一個(gè)用戶(hù)線程仍在運(yùn)行,則程序繼續(xù)運(yùn)行;而如果其他線程均為守護(hù)線程,則程序終止運(yùn)行。5.1.4線程的狀態(tài)與生命周期
每個(gè)Java程序都有一個(gè)缺省的主線程。對(duì)于Java應(yīng)用程序,主線程是main方法執(zhí)行的指令序列;對(duì)于Applet,主線程指揮瀏覽器加載并執(zhí)行Java小程序。要想實(shí)現(xiàn)多線程,必須創(chuàng)建新的線程對(duì)象。
Java語(yǔ)言使用Thread類(lèi)及其子類(lèi)的對(duì)象來(lái)表示線程,新建的線程在它的一個(gè)完整的生命周期中通常包括新生、運(yùn)行、睡眠和死亡四種狀態(tài)。
(1)新生(New)狀態(tài):線程被創(chuàng)建但尚未啟動(dòng)其指定的指令序列時(shí),線程處于New狀態(tài)。
(2)運(yùn)行(Runnable)狀態(tài):處于新生狀態(tài)的線程被啟動(dòng)后,進(jìn)入Runnable狀態(tài)。Runnable狀態(tài)可分為就緒和運(yùn)行兩種狀態(tài),但從程序設(shè)計(jì)的角度來(lái)看,可以認(rèn)為它們是同一種狀態(tài)。
(3)睡眠(NotRunning)狀態(tài):一個(gè)正在執(zhí)行的線程在某些特殊情況下,暫停執(zhí)行,進(jìn)入睡眠狀態(tài)。例如線程因執(zhí)行I/O操作而阻塞時(shí),必須等待I/O操作結(jié)束。另外線程也可以主動(dòng)調(diào)用sleep方法進(jìn)入睡眠狀態(tài)。
(4)死亡狀態(tài):處于死亡狀態(tài)的線程不具有繼續(xù)運(yùn)行的能力。線程死亡的原因有兩個(gè):一個(gè)是正常運(yùn)行的線程完成它的全部工作后退出;另一個(gè)則是線程被強(qiáng)制性地終止,如通過(guò)執(zhí)行stop方法或destroy來(lái)終止線程。
5.2多線程的實(shí)現(xiàn)方法
5.2.1線程類(lèi)Thread
Java語(yǔ)言是第一個(gè)直接支持多線程的程序設(shè)計(jì)語(yǔ)言。用Java語(yǔ)言編寫(xiě)多線程程序無(wú)需直接訪問(wèn)操作系統(tǒng)的編程接口,它提供了類(lèi)java.lang.Thread方便多線程編程,創(chuàng)建一個(gè)新的線程只需指明這個(gè)線程所要執(zhí)行的代碼即可。
Java虛擬機(jī)本身并不直接實(shí)現(xiàn)線程機(jī)制,它仍然需要操作系統(tǒng)的支持,不過(guò)所有需要訪問(wèn)操作系統(tǒng)編程接口的工作都已經(jīng)在Thread類(lèi)以及其他幾個(gè)與線程有關(guān)的類(lèi)中實(shí)現(xiàn)了。
Thread類(lèi)的構(gòu)造方法有多種,它們是:
●?publicThread();
●?publicThread(Runnabletarget);
●?publicThread(Runnabletarget,Stringname);
●?publicThread(Stringname);
●?publicThread(ThreadGroupgroup,Runnabletarget);
●?publicThread(ThreadGroupgroup,Runnabletarget,Stringname);
●?publicThread(ThreadGroupgroup,Stringname)。其中,target為Runnable接口類(lèi)型的對(duì)象,用于提供該線程執(zhí)行的指令序列,有關(guān)Runnable接口的使用將在下面詳細(xì)討論;name為新線程的名稱(chēng);group參數(shù)為線程組,線程組類(lèi)ThreadGroup是Java語(yǔ)言為方便線程的調(diào)度管理定義的一個(gè)類(lèi),可以將若干個(gè)線程加入同一個(gè)線程組。
Thread類(lèi)提供了大量的方法來(lái)控制線程,下面介紹幾個(gè)主要的方法。
●?publicstaticintactiveCount();——返回線程組中當(dāng)前活動(dòng)的線程數(shù)。
●?publicstaticnativeThreadcurrentThread();——返回當(dāng)前運(yùn)行的Thread對(duì)象?!?publicvoiddestroy();——破壞線程,但不進(jìn)行清理。
●?publicfinalStringgetName();——返回線程的名字。
●?publicfinalintgetPriority();——返回線程優(yōu)先級(jí)。
●?publicfinalThreadGroupgetThreadGroup();——得到線程所屬的線程組。
●?publicvoidinterrupt();——中斷線程運(yùn)行。
●?publicstaticbooleaninterrupted();——判斷當(dāng)前線程是否已被中斷。
●?publicbooleanisInterrupted();——判斷線程是否已被中斷?!?publicfinalnativebooleanisAlive();——測(cè)試線程是否處于活動(dòng)狀態(tài)。
●?publicfinalbooleanisDaemon();——方法isDaemon判斷一個(gè)線程是否是守護(hù)線程。
●?publicvoidrun();——指定線程需要運(yùn)行的代碼,一般由派生類(lèi)或Runnable接口覆蓋Thread類(lèi)中定義的該方法。
●?publicfinalvoidsetDaemon(booleanon);——方法setDaemon將一個(gè)線程設(shè)為守護(hù)線程(on為true)或用戶(hù)線程(on為false),該方法必須在線程啟動(dòng)前調(diào)用。
●?publicfinalvoidsetName(Stringname);——設(shè)置線程名。●?publicfinalvoidsetPriority(intnewPriority);——設(shè)置線程優(yōu)先級(jí)。
●?publicstaticvoidsleep(longmillis);。
●?publicstaticvoidsleep(longmillis,intnanos);。
以上兩種方法用于使線程在指定的時(shí)間內(nèi)進(jìn)入睡眠狀態(tài),指定的時(shí)間一過(guò),線程重新進(jìn)入執(zhí)行狀態(tài)。millis為毫秒數(shù),nanos為納秒數(shù)。
●?publicsynchronziednativevoidstart();——開(kāi)始運(yùn)行線程,Java虛擬機(jī)將自動(dòng)調(diào)用線程的run方法。
●?publicstaticvoidyield();——使線程放棄當(dāng)前分得的CPU時(shí)間,但不進(jìn)入睡眠狀態(tài),仍處于可執(zhí)行狀態(tài)。將CPU控制權(quán)主動(dòng)移交到下一個(gè)可運(yùn)行線程。5.2.2繼承Thread類(lèi)
從上面的介紹可以知道,線程類(lèi)中定義了一個(gè)方法run,該方法稱(chēng)為線程體,用于指定線程所要執(zhí)行的指令序列。創(chuàng)建一個(gè)線程類(lèi)對(duì)象,調(diào)用start方法啟動(dòng)線程后,run方法會(huì)被自動(dòng)調(diào)用。如果在Thread類(lèi)的子類(lèi)中覆蓋run方法,加入線程所要執(zhí)行的代碼,則線程啟動(dòng)后,子類(lèi)中定義的run方法被調(diào)用。這是Java語(yǔ)言中實(shí)現(xiàn)多線程的第一種方法,也是最簡(jiǎn)單直接的方法。繼承Thread類(lèi)的基本步驟如下:
(1)從Thread類(lèi)派生一個(gè)類(lèi),覆蓋Thread類(lèi)中的run方法。例如:
publicclassD_ThreadextendsThread{
publicvoidrun(){
//需要以線程方式運(yùn)行的代碼
}
}
(2)創(chuàng)建該派生類(lèi)的對(duì)象。例如:
D_ThreadnewThread=newD_Thread();
(3)調(diào)用start方法啟動(dòng)該線程。例如:
newThread.start();【程序5.1】派生Thread類(lèi)實(shí)現(xiàn)多線程。
importjava.lang.InterruptedException;
publicclassThreadTest{
publicstaticvoidmain(String[]args)
{
MyThreadfirst,second;
first=newMyThread("Thefirstthread");
second=newMyThread("Thesecondthread");
first.start();
second.start();
}}
classMyThreadextendsThread
{
Stringname;
publicMyThread(Stringname)
{
=name;
System.out.println(name+"created");
}
publicvoidrun() {
System.out.println(name+"Started");
try{
sleep(1000);
}catch(InterruptedExceptione){}
System.out.println(name+"finished");
}
}
程序輸出結(jié)果為
Thefirstthreadcreated
Thesecondthreadcreated
ThefirstthreadStarted
ThesecondthreadStarted
Thefirstthreadfinished
Thesecondthreadfinished
sleep方法在執(zhí)行時(shí)如果發(fā)生錯(cuò)誤,會(huì)拋擲異常,程序中采用try-catch捕捉該異常,有關(guān)語(yǔ)法將在第7章詳細(xì)介紹。5.2.3實(shí)現(xiàn)Runnable接口
5.2.2節(jié)的方法簡(jiǎn)單明了,但有一個(gè)很大的缺點(diǎn),即如果用戶(hù)定義的類(lèi)已經(jīng)從一個(gè)類(lèi)繼承(例如Applet必須繼承自Applet類(lèi)),則無(wú)法再繼承Thread類(lèi)。Java語(yǔ)言中可以直接創(chuàng)建Thread類(lèi)的對(duì)象,而線程體通過(guò)target參數(shù)指定。從Thread類(lèi)的構(gòu)造方法可以看出,target參數(shù)的類(lèi)型為Runnable。Runnable接口只有一個(gè)方法run,用戶(hù)自定義的類(lèi)只需實(shí)現(xiàn)Runnable接口,將線程體寫(xiě)入run方法,然后創(chuàng)建該類(lèi)對(duì)象并將該對(duì)象傳遞給Thread類(lèi)的構(gòu)造方法。
使用Runnable接口實(shí)現(xiàn)多線程的基本步驟如下:
(1)定義一個(gè)類(lèi),實(shí)現(xiàn)Runnable接口。例如:
publicclassI_ThreadimplementsRunnable{
publicvoidrun(){
//線程體
}
}
(2)創(chuàng)建自定義類(lèi)的對(duì)象。例如:
I_Threadtarget=newI_Thread();
(3)創(chuàng)建Thread類(lèi)對(duì)象,指定該類(lèi)的對(duì)象作target參數(shù)。例如:
Threadnewthread=newThread(target);
(4)啟動(dòng)線程。例如:
newthread.start();
程序5.2實(shí)現(xiàn)的功能與程序5.1相同,讀者可比較一下兩個(gè)程序的不同點(diǎn)。
【程序5.2】通過(guò)Runnable接口實(shí)現(xiàn)多線程。
importjava.lang.InterruptedException;
publicclassThreadTest
{
publicstaticvoidmain(String[]args)
{ MyTargetfirsttarget,secondtarget;
firsttarget=newMyTarget("Thefirstthread");
secondtarget=newMyTarget("Thesecondthread");
Threadfirst,second;
first=newThread(firsttarget);
second=newThread(secondtarget);
first.start();
second.start();
}
}classMyTargetimplementsRunnable
{
Stringname;
publicMyTarget(Stringname)
{
=name;
System.out.println(name+"created");
}
publicvoidrun()
{ System.out.println(name+"Started");
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){}
System.out.println(name+"finished");
}
}
5.3采用多線程實(shí)現(xiàn)動(dòng)畫(huà)效果
WWW最初被引入Internet時(shí)能提供的都是靜態(tài)的網(wǎng)頁(yè),而JavaApplet最吸引人的就是可以嵌入在網(wǎng)頁(yè)中運(yùn)行,使網(wǎng)頁(yè)變得生動(dòng)精彩。使用Applet顯示動(dòng)畫(huà)是最常見(jiàn)的一種應(yīng)用,本節(jié)介紹實(shí)現(xiàn)動(dòng)畫(huà)的基本方法。
動(dòng)畫(huà)的實(shí)現(xiàn)原理很簡(jiǎn)單。首先在屏幕上顯示動(dòng)畫(huà)的第一幀(也就是第一幅畫(huà)面),然后每隔一段時(shí)間顯示另外一幀,如此往復(fù),由于人眼的視覺(jué)暫停而感覺(jué)好像畫(huà)面中的物體在運(yùn)動(dòng)。畫(huà)面的繪制是由paint方法完成的,每過(guò)一定的時(shí)間必須重新調(diào)用paint。程序5.3采用一個(gè)獨(dú)立的線程,定時(shí)通知主線程刷新顯示。【程序5.3】用多線程技術(shù)實(shí)現(xiàn)動(dòng)畫(huà)(運(yùn)行畫(huà)面如圖5.1所示)。
importjava.awt.*;
importjava.applet.*;
importjava.util.Date; //Date類(lèi)用于處理日期時(shí)間信息
publicclassAppletClockextendsAppletimplementsRunnable
{
Datetimenow;
Threadclockthread=null; //設(shè)置一個(gè)線程
publicvoidstart()
{
if(clockthread==null) //如果線程為空,則
{
clockthread=newThread(this); //創(chuàng)建新線程,target為當(dāng)前Applet
clockthread.start(); //啟動(dòng)新線程
}
} publicvoidstop()
{
clockthread.stop(); //終止線程
clockthread=null;
}
publicvoidrun() //線程體
{
while(true){ repaint(); //重新繪制
try{
Thread.sleep(1000); //線程睡眠1000ms
}catch(InterruptedExceptione){} //捕獲異常
}
} publicvoidpaint(Graphicsg)
{
timenow=newDate(); //獲得當(dāng)前時(shí)間
g.drawString(timenow.toString(),25,30); //將它打印出來(lái)
}
}圖5.1程序5.3的運(yùn)行畫(huà)面
5.4線程的同步與死鎖
5.4.1同步的概念
同一進(jìn)程的多個(gè)線程共享同一存儲(chǔ)空間,帶來(lái)了訪問(wèn)沖突這個(gè)嚴(yán)重的問(wèn)題。例如,兩個(gè)線程訪問(wèn)同一個(gè)對(duì)象,一個(gè)線程向?qū)ο笾写鎯?chǔ)數(shù)據(jù),另一個(gè)線程讀取該數(shù)據(jù)。如果第一個(gè)線程還沒(méi)有完成存儲(chǔ)操作第二個(gè)線程就開(kāi)始讀數(shù)據(jù),就產(chǎn)生了混亂。因此,必須采用同步機(jī)制來(lái)防止類(lèi)似情況發(fā)生,即在第一個(gè)線程完成存儲(chǔ)操作之前,禁止其他線程訪問(wèn)該
對(duì)象。【程序5.4】未使用同步機(jī)制的多線程程序。
classDataClass{
privateintdata=0;
publicvoidIncrease(){
intnd=data;
try{
Thread.sleep(100);
}catch(Exceptione){}
data=nd+1;
}
publicintGetData(){returndata;}}
classNThreadextendsThread{
DataClassd;
NThread(DataClassd){this.d=d;}
booleanalive=true;
publicvoidrun()
{
for(inti=0;i<100;i++)
d.Increase();
alive=false; }
}
publicclassNoSyn
{
publicstaticvoidmain(String[]args)
{
DataClassd=newDataClass();
NThreadt1=newNThread(d);
NThreadt2=newNThread(d);
t1.start();
t2.start(); while(t1.alive||t2.alive);
System.out.println(“data=”+d.GetData());
}
}
程序5.4在NoSyn類(lèi)的main方法中創(chuàng)建了兩個(gè)線程t1和t2,兩個(gè)線程分別對(duì)DataClass類(lèi)的對(duì)象d調(diào)用100次Increase方法。當(dāng)兩個(gè)線程的run方法執(zhí)行結(jié)束后,main方法輸出對(duì)象d的數(shù)據(jù)成員data。按照程序的功能,data最后的值應(yīng)為200,但實(shí)際運(yùn)行后輸出的結(jié)果并非如此。程序5.4中DataClass的方法Increase在取出變量成員data的值后,暫停100ms,然后將原來(lái)的值加1寫(xiě)回data。在線程t1寫(xiě)回data之前,線程t2取data的值為原來(lái)的值,而不是線程t1計(jì)算后的值,由此引起錯(cuò)誤的結(jié)果。當(dāng)然,在實(shí)際的應(yīng)用系統(tǒng)中,取data值后一般不會(huì)進(jìn)入睡眠狀態(tài),而可能是一些復(fù)雜費(fèi)時(shí)的計(jì)算,每次的運(yùn)行結(jié)果可能不一樣。
程序5.4的運(yùn)行結(jié)果很顯然是用戶(hù)不希望看到的。為了解決這個(gè)問(wèn)題,Java語(yǔ)言提供了專(zhuān)門(mén)機(jī)制來(lái)解決這種沖突,避免同一個(gè)數(shù)據(jù)對(duì)象被多個(gè)線程同時(shí)訪問(wèn)。為此,Java語(yǔ)言引入了一個(gè)關(guān)鍵字synchronized,它有兩種用法:synchronized方法和synchronized塊。5.4.2synchronized方法
通過(guò)在方法聲明中加入synchronized關(guān)鍵字來(lái)聲明synchronized方法。例如:
publicsynchronizedvoidaccessVal(intnewVal);
synchronized方法控制對(duì)對(duì)象成員的訪問(wèn),每個(gè)對(duì)象對(duì)應(yīng)一把鎖。每個(gè)synchronized方法都必須獲得調(diào)用該方法的對(duì)象的鎖方能執(zhí)行,否則所屬線程阻塞。synchronized方法一旦執(zhí)行,就獨(dú)占該鎖,直到從該方法返回時(shí)才將鎖釋放,此后被阻塞的線程方能獲得該鎖進(jìn)入可執(zhí)行狀態(tài)。
Java語(yǔ)言的這種同步機(jī)制確保了同一時(shí)刻對(duì)于每一個(gè)該類(lèi)對(duì)象,synchronized方法成員至多只有一個(gè)處于可執(zhí)行狀態(tài),從而有效避免了對(duì)對(duì)象成員的訪問(wèn)沖突。
程序5.5對(duì)程序5.4進(jìn)行了修改,將DataClass類(lèi)的方法成員Increase定義為synchronized的,則當(dāng)線程t1執(zhí)行d.Increse()時(shí),鎖定對(duì)象d,此時(shí)如果線程t2執(zhí)行d.Increase(),則被阻塞,進(jìn)入休眠狀態(tài)。程序5.5執(zhí)行后輸出正確的結(jié)果,data的值為200?!境绦?.5】使用synchronized方法的多線程程序。
classDataClass{
privateintdata=0;
synchronizedpublicvoidIncrease()
{
intnd=data;
try{
Thread.sleep(100);
}catch(Exceptione){}
data=nd+1;
} publicintGetData()
{
returndata;
}
}
classNThreadextendsThread{
DataClassd;
NThread(DataClassd){this.d=d;}
booleanalive=true;
publicvoidrun()
{ for(inti=0;i<100;i++)
d.Increase();
alive=false;
}
}
publicclassSynDemo
{
publicstaticvoidmain(String[]args)
{
DataClassd=newDataClass();
NThreadt1=newNThread(d); NThreadt2=newNThread(d);
t1.start();
t2.start();
while(t1.alive||t2.alive);
System.out.println(“data=”+d.GetData());
}
}
在Java中,不僅是對(duì)象,每一個(gè)類(lèi)也對(duì)應(yīng)一把鎖??蓪㈩?lèi)的靜態(tài)方法聲明為synchronized,以控制其對(duì)類(lèi)的靜態(tài)成員變量的訪問(wèn)。5.4.3synchronized塊
synchronized方法雖然可以解決同步的問(wèn)題,但也存在缺陷,如果一個(gè)synchronized方法需要執(zhí)行很長(zhǎng)時(shí)間,將會(huì)大大影響系統(tǒng)的效率。Java語(yǔ)言提供了一種解決辦法,就是synchronized塊??梢酝ㄟ^(guò)synchronized關(guān)鍵字將一個(gè)程序塊聲明為synchronized塊,而不是整個(gè)方法聲明為synchronized方法。
synchronized塊的語(yǔ)法如下:
synchronized(syncObject){
//允許訪問(wèn)控制的代碼
}
synchronized塊中的代碼必須獲得對(duì)象syncObject(可以是類(lèi)實(shí)例或類(lèi))的鎖才能執(zhí)行,具體機(jī)制與synchronized方法相同。由于可以針對(duì)任意代碼塊,且可任意指定上鎖的對(duì)象,故靈活性較高。
程序5.6演示了如何使用synchronized塊。
【程序5.6】使用Synchronized塊的多線程程序。
classCallme
{
voidcall(Stringmsg)
{
synchronized(this){ System.out.print("["+msg);
try{
Thread.sleep(1000);
}catch(Exceptione){}
System.out.println("]");
}
}
}
classcallerimplementsRunnable
{
Stringmsg; Callmetarget;
publiccaller(Callmet,Strings)
{
target=t;
msg=s;
newThread(this).start();
}
publicvoidrun()
{
target.call(msg);
}}
publicclassSynBlock
{
publicstaticvoidmain(String[]args)
{
Callmetarget=newCallme();
newcaller(target,"Hello");
newcaller(target,"Synchronized");
newcaller(target,"World");
}
}程序運(yùn)行結(jié)果為
[Hello]
[Synchronized]
[World]
5.4.4線程的死鎖
線程同步雖然解決了對(duì)象訪問(wèn)的沖突,但同步可能帶來(lái)其他的問(wèn)題,死鎖就是其中之一。在編寫(xiě)多線程程序時(shí)特別要注意防止死鎖。當(dāng)多個(gè)線程在一個(gè)給定的任務(wù)中協(xié)同作用、互相干涉,從而導(dǎo)致一個(gè)或者更多線程永遠(yuǎn)阻塞時(shí),死鎖就發(fā)生了。例如,一個(gè)線程擁有對(duì)象A,另一個(gè)線程擁有對(duì)象B,第一個(gè)線程必須擁有對(duì)象B才能繼續(xù),同樣第二個(gè)線程必須擁有對(duì)象A才能繼續(xù),兩個(gè)線程相互等待對(duì)方釋放當(dāng)前擁有的對(duì)象,造成兩個(gè)線程阻塞,發(fā)生死鎖。
程序5.7演示了死鎖發(fā)生的過(guò)程。主線程首先創(chuàng)建一個(gè)新的線程并啟動(dòng),該線程執(zhí)行b.CallA(),鎖定對(duì)象b;然后調(diào)用a.CallB(),鎖定對(duì)象a。CallA()方法等待對(duì)象a才能返回,而CallB()方法等待對(duì)象b才能返回,如此相互等待,引起死鎖。【程序5.7】死鎖的發(fā)生。
classA{
synchronizedvoidPrint()
{
System.out.println("APrint");
}
synchronizedvoid
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 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ì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度公司與員工租車(chē)及費(fèi)用結(jié)算協(xié)議3篇
- 二零二五年度企業(yè)勞動(dòng)合同解除與離職員工經(jīng)濟(jì)補(bǔ)償及就業(yè)權(quán)益維護(hù)協(xié)議3篇
- 二零二五年度公園水泥路施工與歷史文化保護(hù)合同3篇
- 二零二五年度公寓租賃糾紛調(diào)解服務(wù)合同樣本3篇
- 2025年度農(nóng)產(chǎn)品種植收購(gòu)與冷鏈物流服務(wù)合同3篇
- 二零二五年度內(nèi)墻乳膠漆涂料行業(yè)市場(chǎng)分析合同3篇
- 2025年度籃球運(yùn)動(dòng)員轉(zhuǎn)會(huì)合同糾紛解決協(xié)議3篇
- 二零二五年度家庭月嫂服務(wù)及培訓(xùn)合同3篇
- 二零二五年度光伏發(fā)電系統(tǒng)安裝合同安裝協(xié)議3篇
- 2025年度度假酒店整體資產(chǎn)及運(yùn)營(yíng)權(quán)轉(zhuǎn)讓合同3篇
- 2024年安防監(jiān)控系統(tǒng)技術(shù)標(biāo)準(zhǔn)與規(guī)范
- 軟件正版化概念培訓(xùn)
- 2024-2025學(xué)年人教版道法八年級(jí)上冊(cè) 第一學(xué)期期末測(cè)試卷01
- 運(yùn)輸公司安全生產(chǎn)隱患排查制度
- 譯林新版(2024)七年級(jí)英語(yǔ)上冊(cè)Unit 5 Reading課件
- 爆破設(shè)計(jì)說(shuō)明書(shū)(修改)
- 2025屆天津市南開(kāi)區(qū)南開(kāi)中學(xué)語(yǔ)文高三上期末達(dá)標(biāo)檢測(cè)試題含解析
- 期末試卷(試題)-2024-2025學(xué)年四年級(jí)上冊(cè)數(shù)學(xué)滬教版
- 光伏電站運(yùn)維詳細(xì)版手冊(cè)
- 藝術(shù)學(xué)概論第一章-彭吉象
- 51job在線測(cè)評(píng)題集
評(píng)論
0/150
提交評(píng)論