版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第5章多線程5.1線程的概念5.2多線程的實現(xiàn)方法5.3采用多線程實現(xiàn)動畫效果5.4線程的同步與死鎖 5.1線?程?的?概?念
5.1.1線程與多線程
線程是指程序中順序執(zhí)行的一個指令序列,多線程允許在程序中并發(fā)執(zhí)行多個指令序列,且彼此間互相獨立。
多線程允許將程序任務分成幾個并行的子任務,以提高系統(tǒng)的運行效率。例如,在網絡編程中,很多功能是可以并發(fā)執(zhí)行的。如果需要從ftp文件服務器下載文件,由于網絡傳輸速度較慢,客戶端提出請求后,須等待服務器響應,此時客戶端處于閑置等待狀態(tài)。如果用兩個獨立的線程去完成該功能,當一個線程處于等待狀態(tài)時,另一個線程可以建立連接,請求另一部分數(shù)據(jù)。采用多線程可以充分利用網絡帶寬,提高文件下載的速度。在圖形用戶界面的程序中,主程序不斷檢測發(fā)生的事件,根據(jù)事件的不同種類調用事件的響應程序。如果采用單線程,只有當事件響應程序執(zhí)行完畢,主程序才能繼續(xù)處理其他的事件。
多個線程的執(zhí)行是并發(fā)的,也就是在邏輯上“同時”,而不管是否是物理上的“同時”。如果系統(tǒng)只有一個CPU,那么真正的“同時”是不可能的,但是由于CPU的速度非???,用戶感覺不到其中的區(qū)別,只需要設想各個線程是同時執(zhí)行即可。
多線程和傳統(tǒng)的單線程在程序設計上最大的區(qū)別在于:由于各個線程的控制流彼此獨立,使得各個線程之間代碼執(zhí)行的順序不確定,由此帶來線程調度、同步等問題。5.1.2進程與線程
進程是程序的一次動態(tài)執(zhí)行過程,它對應了從代碼加載、執(zhí)行,到執(zhí)行完畢的一個完整過程,這個過程也是進程本身從產生、發(fā)展到消亡的過程。同一個程序可以被加載到系統(tǒng)的不同內存區(qū)域分別執(zhí)行,形成不同的進程。
線程是比進程更小的執(zhí)行單位。一個進程在其執(zhí)行過程中可以產生多個線程,每個線程也有它自身的產生、存在和消亡過程,是一個動態(tài)的概念。進程之間的內部數(shù)據(jù)和狀態(tài)都是完全獨立的,同一進程的多個線程共享一塊內存空間和一組系統(tǒng)資源,有可能互相影響。線程切換時保存其內部狀態(tài)的數(shù)據(jù)較少,切換的負擔比進程切換要小。5.1.3線程的優(yōu)先級與類別
每一個線程都有一個優(yōu)先級,Java將線程的優(yōu)先級分為10個等級,分別用1~10之間的數(shù)字表示。數(shù)字越大表明線程的優(yōu)先級越高,缺省情況下的優(yōu)先級為5。Java語言在線程類Thread中定義了表示線程最低、最高和普通優(yōu)先級的常量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的優(yōu)先級等級分別為1、10和5。為了控制線程的運行,Java定義了線程調度器來監(jiān)控系統(tǒng)中處于就緒狀態(tài)的所有線程。線程調度器按照線程的優(yōu)先級決定哪個線程投入處理器運行。在多個線程處于就緒狀態(tài)的條件下,具有高優(yōu)先級的線程會在低優(yōu)先級線程之前得到執(zhí)行。具有相同優(yōu)先級的所有線程采用輪轉的方式來共同分配CPU時間。
Java語言中的線程分為兩類,即用戶線程和守護(Daemon)線程。守護線程具有最低的優(yōu)先級,用于為系統(tǒng)中的其他對象和線程提供服務。典型的守護線程的例子是Java虛擬機中的系統(tǒng)資源自動回收線程,它始終在低級別的狀態(tài)中運行,用于實時監(jiān)控和管理系統(tǒng)中的可回收資源。
Java程序運行到所有用戶線程終止,然后終止所有的守護線程。對一個Java應用程序,main方法運行結束以后,如果另一個用戶線程仍在運行,則程序繼續(xù)運行;而如果其他線程均為守護線程,則程序終止運行。5.1.4線程的狀態(tài)與生命周期
每個Java程序都有一個缺省的主線程。對于Java應用程序,主線程是main方法執(zhí)行的指令序列;對于Applet,主線程指揮瀏覽器加載并執(zhí)行Java小程序。要想實現(xiàn)多線程,必須創(chuàng)建新的線程對象。
Java語言使用Thread類及其子類的對象來表示線程,新建的線程在它的一個完整的生命周期中通常包括新生、運行、睡眠和死亡四種狀態(tài)。
(1)新生(New)狀態(tài):線程被創(chuàng)建但尚未啟動其指定的指令序列時,線程處于New狀態(tài)。
(2)運行(Runnable)狀態(tài):處于新生狀態(tài)的線程被啟動后,進入Runnable狀態(tài)。Runnable狀態(tài)可分為就緒和運行兩種狀態(tài),但從程序設計的角度來看,可以認為它們是同一種狀態(tài)。
(3)睡眠(NotRunning)狀態(tài):一個正在執(zhí)行的線程在某些特殊情況下,暫停執(zhí)行,進入睡眠狀態(tài)。例如線程因執(zhí)行I/O操作而阻塞時,必須等待I/O操作結束。另外線程也可以主動調用sleep方法進入睡眠狀態(tài)。
(4)死亡狀態(tài):處于死亡狀態(tài)的線程不具有繼續(xù)運行的能力。線程死亡的原因有兩個:一個是正常運行的線程完成它的全部工作后退出;另一個則是線程被強制性地終止,如通過執(zhí)行stop方法或destroy來終止線程。
5.2多線程的實現(xiàn)方法
5.2.1線程類Thread
Java語言是第一個直接支持多線程的程序設計語言。用Java語言編寫多線程程序無需直接訪問操作系統(tǒng)的編程接口,它提供了類java.lang.Thread方便多線程編程,創(chuàng)建一個新的線程只需指明這個線程所要執(zhí)行的代碼即可。
Java虛擬機本身并不直接實現(xiàn)線程機制,它仍然需要操作系統(tǒng)的支持,不過所有需要訪問操作系統(tǒng)編程接口的工作都已經在Thread類以及其他幾個與線程有關的類中實現(xiàn)了。
Thread類的構造方法有多種,它們是:
●?publicThread();
●?publicThread(Runnabletarget);
●?publicThread(Runnabletarget,Stringname);
●?publicThread(Stringname);
●?publicThread(ThreadGroupgroup,Runnabletarget);
●?publicThread(ThreadGroupgroup,Runnabletarget,Stringname);
●?publicThread(ThreadGroupgroup,Stringname)。其中,target為Runnable接口類型的對象,用于提供該線程執(zhí)行的指令序列,有關Runnable接口的使用將在下面詳細討論;name為新線程的名稱;group參數(shù)為線程組,線程組類ThreadGroup是Java語言為方便線程的調度管理定義的一個類,可以將若干個線程加入同一個線程組。
Thread類提供了大量的方法來控制線程,下面介紹幾個主要的方法。
●?publicstaticintactiveCount();——返回線程組中當前活動的線程數(shù)。
●?publicstaticnativeThreadcurrentThread();——返回當前運行的Thread對象?!?publicvoiddestroy();——破壞線程,但不進行清理。
●?publicfinalStringgetName();——返回線程的名字。
●?publicfinalintgetPriority();——返回線程優(yōu)先級。
●?publicfinalThreadGroupgetThreadGroup();——得到線程所屬的線程組。
●?publicvoidinterrupt();——中斷線程運行。
●?publicstaticbooleaninterrupted();——判斷當前線程是否已被中斷。
●?publicbooleanisInterrupted();——判斷線程是否已被中斷?!?publicfinalnativebooleanisAlive();——測試線程是否處于活動狀態(tài)。
●?publicfinalbooleanisDaemon();——方法isDaemon判斷一個線程是否是守護線程。
●?publicvoidrun();——指定線程需要運行的代碼,一般由派生類或Runnable接口覆蓋Thread類中定義的該方法。
●?publicfinalvoidsetDaemon(booleanon);——方法setDaemon將一個線程設為守護線程(on為true)或用戶線程(on為false),該方法必須在線程啟動前調用。
●?publicfinalvoidsetName(Stringname);——設置線程名?!?publicfinalvoidsetPriority(intnewPriority);——設置線程優(yōu)先級。
●?publicstaticvoidsleep(longmillis);。
●?publicstaticvoidsleep(longmillis,intnanos);。
以上兩種方法用于使線程在指定的時間內進入睡眠狀態(tài),指定的時間一過,線程重新進入執(zhí)行狀態(tài)。millis為毫秒數(shù),nanos為納秒數(shù)。
●?publicsynchronziednativevoidstart();——開始運行線程,Java虛擬機將自動調用線程的run方法。
●?publicstaticvoidyield();——使線程放棄當前分得的CPU時間,但不進入睡眠狀態(tài),仍處于可執(zhí)行狀態(tài)。將CPU控制權主動移交到下一個可運行線程。5.2.2繼承Thread類
從上面的介紹可以知道,線程類中定義了一個方法run,該方法稱為線程體,用于指定線程所要執(zhí)行的指令序列。創(chuàng)建一個線程類對象,調用start方法啟動線程后,run方法會被自動調用。如果在Thread類的子類中覆蓋run方法,加入線程所要執(zhí)行的代碼,則線程啟動后,子類中定義的run方法被調用。這是Java語言中實現(xiàn)多線程的第一種方法,也是最簡單直接的方法。繼承Thread類的基本步驟如下:
(1)從Thread類派生一個類,覆蓋Thread類中的run方法。例如:
publicclassD_ThreadextendsThread{
publicvoidrun(){
//需要以線程方式運行的代碼
}
}
(2)創(chuàng)建該派生類的對象。例如:
D_ThreadnewThread=newD_Thread();
(3)調用start方法啟動該線程。例如:
newThread.start();【程序5.1】派生Thread類實現(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");
}
}
程序輸出結果為
Thefirstthreadcreated
Thesecondthreadcreated
ThefirstthreadStarted
ThesecondthreadStarted
Thefirstthreadfinished
Thesecondthreadfinished
sleep方法在執(zhí)行時如果發(fā)生錯誤,會拋擲異常,程序中采用try-catch捕捉該異常,有關語法將在第7章詳細介紹。5.2.3實現(xiàn)Runnable接口
5.2.2節(jié)的方法簡單明了,但有一個很大的缺點,即如果用戶定義的類已經從一個類繼承(例如Applet必須繼承自Applet類),則無法再繼承Thread類。Java語言中可以直接創(chuàng)建Thread類的對象,而線程體通過target參數(shù)指定。從Thread類的構造方法可以看出,target參數(shù)的類型為Runnable。Runnable接口只有一個方法run,用戶自定義的類只需實現(xiàn)Runnable接口,將線程體寫入run方法,然后創(chuàng)建該類對象并將該對象傳遞給Thread類的構造方法。
使用Runnable接口實現(xiàn)多線程的基本步驟如下:
(1)定義一個類,實現(xiàn)Runnable接口。例如:
publicclassI_ThreadimplementsRunnable{
publicvoidrun(){
//線程體
}
}
(2)創(chuàng)建自定義類的對象。例如:
I_Threadtarget=newI_Thread();
(3)創(chuàng)建Thread類對象,指定該類的對象作target參數(shù)。例如:
Threadnewthread=newThread(target);
(4)啟動線程。例如:
newthread.start();
程序5.2實現(xiàn)的功能與程序5.1相同,讀者可比較一下兩個程序的不同點。
【程序5.2】通過Runnable接口實現(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采用多線程實現(xiàn)動畫效果
WWW最初被引入Internet時能提供的都是靜態(tài)的網頁,而JavaApplet最吸引人的就是可以嵌入在網頁中運行,使網頁變得生動精彩。使用Applet顯示動畫是最常見的一種應用,本節(jié)介紹實現(xiàn)動畫的基本方法。
動畫的實現(xiàn)原理很簡單。首先在屏幕上顯示動畫的第一幀(也就是第一幅畫面),然后每隔一段時間顯示另外一幀,如此往復,由于人眼的視覺暫停而感覺好像畫面中的物體在運動。畫面的繪制是由paint方法完成的,每過一定的時間必須重新調用paint。程序5.3采用一個獨立的線程,定時通知主線程刷新顯示?!境绦?.3】用多線程技術實現(xiàn)動畫(運行畫面如圖5.1所示)。
importjava.awt.*;
importjava.applet.*;
importjava.util.Date; //Date類用于處理日期時間信息
publicclassAppletClockextendsAppletimplementsRunnable
{
Datetimenow;
Threadclockthread=null; //設置一個線程
publicvoidstart()
{
if(clockthread==null) //如果線程為空,則
{
clockthread=newThread(this); //創(chuàng)建新線程,target為當前Applet
clockthread.start(); //啟動新線程
}
} publicvoidstop()
{
clockthread.stop(); //終止線程
clockthread=null;
}
publicvoidrun() //線程體
{
while(true){ repaint(); //重新繪制
try{
Thread.sleep(1000); //線程睡眠1000ms
}catch(InterruptedExceptione){} //捕獲異常
}
} publicvoidpaint(Graphicsg)
{
timenow=newDate(); //獲得當前時間
g.drawString(timenow.toString(),25,30); //將它打印出來
}
}圖5.1程序5.3的運行畫面
5.4線程的同步與死鎖
5.4.1同步的概念
同一進程的多個線程共享同一存儲空間,帶來了訪問沖突這個嚴重的問題。例如,兩個線程訪問同一個對象,一個線程向對象中存儲數(shù)據(jù),另一個線程讀取該數(shù)據(jù)。如果第一個線程還沒有完成存儲操作第二個線程就開始讀數(shù)據(jù),就產生了混亂。因此,必須采用同步機制來防止類似情況發(fā)生,即在第一個線程完成存儲操作之前,禁止其他線程訪問該
對象?!境绦?.4】未使用同步機制的多線程程序。
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類的main方法中創(chuàng)建了兩個線程t1和t2,兩個線程分別對DataClass類的對象d調用100次Increase方法。當兩個線程的run方法執(zhí)行結束后,main方法輸出對象d的數(shù)據(jù)成員data。按照程序的功能,data最后的值應為200,但實際運行后輸出的結果并非如此。程序5.4中DataClass的方法Increase在取出變量成員data的值后,暫停100ms,然后將原來的值加1寫回data。在線程t1寫回data之前,線程t2取data的值為原來的值,而不是線程t1計算后的值,由此引起錯誤的結果。當然,在實際的應用系統(tǒng)中,取data值后一般不會進入睡眠狀態(tài),而可能是一些復雜費時的計算,每次的運行結果可能不一樣。
程序5.4的運行結果很顯然是用戶不希望看到的。為了解決這個問題,Java語言提供了專門機制來解決這種沖突,避免同一個數(shù)據(jù)對象被多個線程同時訪問。為此,Java語言引入了一個關鍵字synchronized,它有兩種用法:synchronized方法和synchronized塊。5.4.2synchronized方法
通過在方法聲明中加入synchronized關鍵字來聲明synchronized方法。例如:
publicsynchronizedvoidaccessVal(intnewVal);
synchronized方法控制對對象成員的訪問,每個對象對應一把鎖。每個synchronized方法都必須獲得調用該方法的對象的鎖方能執(zhí)行,否則所屬線程阻塞。synchronized方法一旦執(zhí)行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖進入可執(zhí)行狀態(tài)。
Java語言的這種同步機制確保了同一時刻對于每一個該類對象,synchronized方法成員至多只有一個處于可執(zhí)行狀態(tài),從而有效避免了對對象成員的訪問沖突。
程序5.5對程序5.4進行了修改,將DataClass類的方法成員Increase定義為synchronized的,則當線程t1執(zhí)行d.Increse()時,鎖定對象d,此時如果線程t2執(zhí)行d.Increase(),則被阻塞,進入休眠狀態(tài)。程序5.5執(zhí)行后輸出正確的結果,data的值為200。【程序5.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中,不僅是對象,每一個類也對應一把鎖。可將類的靜態(tài)方法聲明為synchronized,以控制其對類的靜態(tài)成員變量的訪問。5.4.3synchronized塊
synchronized方法雖然可以解決同步的問題,但也存在缺陷,如果一個synchronized方法需要執(zhí)行很長時間,將會大大影響系統(tǒng)的效率。Java語言提供了一種解決辦法,就是synchronized塊。可以通過synchronized關鍵字將一個程序塊聲明為synchronized塊,而不是整個方法聲明為synchronized方法。
synchronized塊的語法如下:
synchronized(syncObject){
//允許訪問控制的代碼
}
synchronized塊中的代碼必須獲得對象syncObject(可以是類實例或類)的鎖才能執(zhí)行,具體機制與synchronized方法相同。由于可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。
程序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");
}
}程序運行結果為
[Hello]
[Synchronized]
[World]
5.4.4線程的死鎖
線程同步雖然解決了對象訪問的沖突,但同步可能帶來其他的問題,死鎖就是其中之一。在編寫多線程程序時特別要注意防止死鎖。當多個線程在一個給定的任務中協(xié)同作用、互相干涉,從而導致一個或者更多線程永遠阻塞時,死鎖就發(fā)生了。例如,一個線程擁有對象A,另一個線程擁有對象B,第一個線程必須擁有對象B才能繼續(xù),同樣第二個線程必須擁有對象A才能繼續(xù),兩個線程相互等待對方釋放當前擁有的對象,造成兩個線程阻塞,發(fā)生死鎖。
程序5.7演示了死鎖發(fā)生的過程。主線程首先創(chuàng)建一個新的線程并啟動,該線程執(zhí)行b.CallA(),鎖定對象b;然后調用a.CallB(),鎖定對象a。CallA()方法等待對象a才能返回,而CallB()方法等待對象b才能返回,如此相互等待,引起死鎖。【程序5.7】死鎖的發(fā)生。
classA{
synchronizedvoidPrint()
{
System.out.println("APrint");
}
synchronizedvoid
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 保險公司財務審核崗位面試題集
- 律師職位應聘的面試常見問題解析及回答策略
- 交通物流規(guī)劃分析師面試問題集
- 高工面試題集及答案解析
- 2025年5G通信網絡布局可行性研究報告
- 2026屆浙江省七校聯(lián)盟高三上學期一模歷史試題(含答案)
- 2025年數(shù)字化營銷在企業(yè)轉型中的作用可行性研究報告
- 2025年城市綠地生態(tài)修復項目可行性研究報告
- 2025年旅游與運動結合的休閑項目可行性研究報告
- 協(xié)警服務協(xié)議書
- 藥房年終總結及明年計劃
- DBJ51T 189-2022 四川省建設工程施工現(xiàn)場安全資料管理標準
- 黔東南州2024-2025學年度第一學期期末文化水平測試九年級數(shù)學試卷
- 第十單元 改革開放和社會主義現(xiàn)代化建設新時期-高中歷史單元說課稿
- 《工會基礎知識》考試題庫300題(含答案)
- 餐廳制度培訓課件
- 手術間的規(guī)范化管理
- 《中國航母之路》課件
- 高中地理說題-全國二卷
- 非遺資源數(shù)據(jù)庫建設
- 寺廟用工合同協(xié)議書
評論
0/150
提交評論