版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領
文檔簡介
面向服務的計算和web數(shù)據(jù)管理第2章多線程分布式計算從本章開始,我們將不僅學習概念,還學習用編程語言實現(xiàn)這些概念。我們相信通過具體編碼能夠更好的解釋這些概念以及它們的使用。我們假設讀者熟悉面向?qū)ο蟮挠嬎?并且用過一種面向?qū)ο缶幊陶Z言如:C++、Java或者C#編寫過面向?qū)ο蟮某绦?。但?為了使那些沒有用過C#的讀者也能理解本章的內(nèi)容,我們在本章的開始對C#和.NET作簡短的介紹。然后,轉(zhuǎn)移到多線程和多任務的一般性問題上,包括并行處理、同步、死鎖和執(zhí)行順序,這些是分布式計算范型的基礎。為了更好的理解所討論的概念,我們將用Java和C#開發(fā)多線程程序。最后,我們討論分布式計算異常和事件驅(qū)動編程方法。這些概念和技術被廣泛用于分布式面向服務的計算。
本章和本書中其他章節(jié)的聯(lián)系不是很緊密,因為本章提到的大多數(shù)問題和技術都在面向服務開發(fā)環(huán)境中遇到過,因此應用構(gòu)建者可以擺脫這些問題。如果一個服務提供者使用服務軟件,如IIS,大部分問題也都屬于服務軟件的問題。但是如果服務提供者想寫自己的服務,那么學習本章的概念還是很重要的。如果讀者關注使用先進的工具和基礎設施進行面向服務的軟件開發(fā),那么可以跳過本章。
在本節(jié),我們將簡要地介紹C#。如果你熟悉C
#,可以跳過這一節(jié)。
C#和Java都是面向?qū)ο蟮木幊陶Z言,并且它們是用來寫多線程程序以及創(chuàng)建SOC服務的兩個主要編程語言。在Java中,每一個線程通過對象創(chuàng)建。在C#中,一個線程能通過對象或?qū)ο笾械暮瘮?shù)(方法)創(chuàng)建。在面向服務的應用中,服務的組成部分用Java或C#對象定義,用開放標準接口包裝這些對象使其成為服務。2.1C#和.Net介紹
C#繼承了C/C++語言的許多語法,并支持Java的許多特征。它是一種強類型語言并自動進行內(nèi)存回收。.Net通用庫中有一個很大的函數(shù)集支持C#,面向?qū)ο蠛兔嫦蚍盏能浖_發(fā)人員可復用這些函數(shù)。我們假定讀者已經(jīng)掌握了Java或者C++,所以本節(jié)只對C#做簡單介紹。2.1.1C#與.Net入門
微軟的VisualStudio.Net是一種支持多種編程語言和多種編程范型的編程環(huán)境。如圖2.1所示,在第一步編譯中,高級語言的程序編譯成一種叫做中間語言(IL)的低級語言,從表面看,中間語言類似于匯編語言。中間語言程序通過公共語言運行時(CLR)環(huán)境進行管理和執(zhí)行。它類似于Java的字節(jié)碼,目的使CLR獨立于高級程序語言。
圖2.1微軟VisualStudio.Net編程環(huán)境
與Java環(huán)境不同的是,.Net框架與語言無關。雖然C#被認為是它的標志性語言,但是.Net框架并不是為某種特定語言設計的。當開發(fā)者使用他們選擇的高級語言編寫應用程序的時候,可以使用公共框架類庫(FCL)和環(huán)境的功能。公共框架類庫是一個通用系統(tǒng)類型,這使得它可以支持不同的編程語言?;陬愋拖到y(tǒng),任何編程語言,例如X,都可以很容易地集成到系統(tǒng)中。唯一要做的工作是寫一個將X轉(zhuǎn)換成IL的編譯器。中間語言程序由實時(JIT)編譯器和CLR執(zhí)行,JIT編譯器采用邊使用邊編譯的策略,當方法被第一次調(diào)用時,它動態(tài)地為內(nèi)部數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存塊。換句話說,JIT編譯器介于編譯和解釋執(zhí)行之間。
現(xiàn)在讓我們寫一個輸出字符串“Hello,World!”的C#程序。
在VisualStudio.Net環(huán)境下執(zhí)行這個C#程序,遵循以下步驟:
(1)從Windows的“開始”菜單啟動.Net。
(2)選擇.Net中的菜單“文件”—“新建”—“項目...”,一個“新項目”對話框彈出,在這里你可以選擇不同的編程語言,包括C/C++、J#(Java)以及C#。
(3)一旦你在框的左側(cè)選擇C#,就可以進一步選擇一個模板幫助你開發(fā)應用,例如:
①選擇“控制臺應用”啟動文本和基于命令行的編程模板;
②選擇“Windows應用”啟動一個基于表格的應用模板,該模板允許你定義圖形用戶界面。
(4)在同一對話框的底部,你可以選擇項目名稱、選擇保存該項目的路徑、選擇方案名稱,你可以在同一個方案中放入多個工程。
(5)點擊“OK”。
這時一個擁有相應庫(取決于你所選擇的模板)的項目模板將被創(chuàng)建。如果選擇的是C#,當你創(chuàng)建一個新項目時,就能夠在擴展名為.cs的文件中輸入C#程序。
下面的程序展示了一個更復雜的例子。這個程序管理舉重比賽的得分/重量。要求用戶輸入四名運動員的姓名和舉重重量,然后程序輸出獲勝者的姓名和舉重重量。
這個程序展示了一個典型程序中的許多重要問題,包括:輸入、輸出、字符串到整數(shù)的轉(zhuǎn)換;定義整型、字符串、整型數(shù)組的引用、字符串數(shù)組的引用、創(chuàng)建一個數(shù)組對象、循環(huán)、定義靜態(tài)函數(shù)、參數(shù)傳遞和函數(shù)調(diào)用。程序中的注釋提供了更為詳細的解釋。2.1.2C#和C++的比較
與C++相比,C#的特征更類似于Java。表2.1將C#和C++的主要特征作了對比??梢钥闯?C#向Java的自動管理方式靠攏,同時在盡可能的情況下保持C++的特征。
表2.2和表2.3分別列出了C#和C++所支持的數(shù)據(jù)類型。C/C++中一些數(shù)據(jù)類型依賴于機器,因此,這種語言標準只規(guī)定了最小范圍。例如,整數(shù)類型int的長度是16、32還是64Byte,取決于實現(xiàn)該語言的計算機體系結(jié)構(gòu)。
表2.1C++和C#(Java)特征比較
表2.2 C/C++中的數(shù)據(jù)類型
表2.3C#中的數(shù)據(jù)類型另一方面,C#運行在虛擬機.Net框架上,所有的數(shù)據(jù)類型是機器無關的。Int類型的長度總是32個字節(jié)。為了更明確,你可以使用Int32代替,特別是小數(shù)類型,它采用較少的位表示指數(shù)部分,而使用多個位表示浮點數(shù)的小數(shù)部分,使它成為高精度的數(shù)據(jù)類型。小數(shù)類型通常用于貨幣類的計算。
2.1.3名字空間和using指令
名字空間用于類的分組,“using<namespace>”用于引用名字空間的類,就像程序中使用庫函數(shù)一樣。下面的代碼段顯示了C#程序的基本組成。
using<namespace>//usingexistingnamespaceaslibrary
namespacemyNamespace1//definemyownnamespace
classmyclass1{
publicstaticvoidMain(){
…
}classmyclass2{
publicdoublePiVlue(){
…
}
}
C#中不存在C/C++中的頭文件,而是用名字空間引用一組類和庫來代替。代碼的第一行顯示了這種應用。程序員可以定義自己的名字空間,如上面代碼的第二行所示,把多個類歸為一組。一個程序員在同一程序中可定義多個名字空間以防止命名沖突。例如:
namespaceVirtualStore{
namespaceCustomer{
//definecustomerclasses
classShoppingCartOrder(){...}
}
namespaceAdmin{//defineadministrationclasses
classReportGenerator(){...}
}
}
“using”指令告訴編譯器在哪里尋找程序中使用的名字空間中類的成員方法。這些成員方法的使用和庫函數(shù)一樣。例如:
usingVirtualStore;
此外,.Net框架的GUI功能、表格可以通過指令訪問:
usingSystem.Windows.Forms;
...
privateButtonButton1;//classButtonisdefinedinSystem.Windows.Forms;
指令使用的另一種形式如下所示,在程序中對每一個引用作完全限定:
privateSystem.Windows.Forms.ButtonButton1;2.1.4C#中的隊列例子
為了了解C#與C++的具體區(qū)別并編寫C#程序,我們介紹一個C#編寫的隊列例子(Chen,2006)。從這個例子可以看到,當你學習過C++或者Java后,就能很輕松地學習C#。類、對象、構(gòu)造函數(shù)、方法的概念將在后續(xù)章節(jié)中通過示例進一步介紹。
usingSystem;
namespaceQueueApplication{//classisdefinedwithincurlybracket
classQueue{privateInt32queuesize;
protectedInt32[]buffer;//declareareferenceonly
protectedInt32front;
protectedInt32rear;
publicQueue(){//constructor
front=0;rear=0;
queue_size=10;
buffer=newInt32[queue_size];//objectiscreatedhere
}publicQueue(Int32n){//constructor
front=0;rear=0;
queue_size=n;
buffer=newInt32[queue_size];
}
publicvoidenqueue(Int32v){
if(rear<queuesize)
buffer[rear++]=v;
else
if(compact())buffer[rear++]=v;
}
publicInt32dequeue(){
if(front<rear)
returnbuffer[front++];
else{
Console.WriteLine("Error:Queueempty");
return-1;
}
}privateboolcompact(){
if(front==0){
Console.WriteLine("Error:Queueoverflow");
returnfalse;
}
else{
for(Int32i=0;i<rear-front;i++)
buffer[i]=buffer[i+front];
rear=rear-front;
front=0;returntrue;
}
}
}
classPriQueue:Queue//inheritance{
publicPriQueue(Int32n):base(n){}
publicInt32getMax(){
inti,max,imax;
if(front<rear){
max=buffer[front];imax=front;//imaxholdstheindexofcurrentmax
for(i=front;i<rear;i++)
if(max<buffer[i]){max=buffer[i];imax=i;}
for(i=imax;i<rear-1;i++)
buffer[i]=buffer[i+1];//removethemaxvalue
rear=rear-1;
returnmax;
}
else{Console.WriteLine("Error:Queueempty");
return-1;}
}
}
classmyMainClass{
staticvoidMain(){
Console.WriteLine("Pleaseenterthequeuesize");
stringstr=Console.ReadLine();//readastring
Int32n=Convert.ToInt32(str);//convertstringtoint
QueueQ1=newQueue(n);
Q1.enqueue(12);Q1.enqueue(36);
Q1.enqueue(24);
Int32X=Q1.dequeue();
Int32Y=Q1.dequeue();
Int32Z=Q1.dequeue();
Console.WriteLine("X={0}Y={1}Z={2}",X,Y,Z);
PriQueueQ2=newPriQueue(n);
Q2.enqueue(12);
Q2.enqueue(36);
Q2.enqueue(24);X=Q2.getMax();
Y=Q2.dequeue();
Z=Q2.dequeue();
Console.WriteLine("X={0}Y={1}Z={2}",X,Y,Z);
}
}
}
程序執(zhí)行后的結(jié)果如下:
Pleaseenterthequeuesize
5
X=12Y=36Z=24
X=36Y=12Z=24
與上一節(jié)舉重的例子相比,這個例子進一步說明了C#的特性,包括:多個類的名字空間的定義、類的構(gòu)造函數(shù)、類的繼承以及對基類構(gòu)造函數(shù)的擴展。2.1.5C#中的類和對象
和Java一樣,所有的C#程序需要一個唯一的程序入口點,或Main方法,作為類中的成員方法來實現(xiàn)。這不同于C++中的Main函數(shù)不屬于任何類。在C#程序中,Main函數(shù)的位置由編譯器決定,與哪個類定義Main函數(shù)無關。Main函數(shù)必須定義為靜態(tài)函數(shù),但是否接收參數(shù)或返回值是可選的。一個可選的public訪問權(quán)限修飾符通知C#編譯器任何人都可以調(diào)用這個成員方法。static關鍵字意味著調(diào)用Main函數(shù)不需要對象實例。很明顯,Main函數(shù)是第一個被執(zhí)行的方法,并且沒有其他的方法能調(diào)用Main函數(shù)。
類是一種用戶自定義類型,一種結(jié)構(gòu)化設計,一種具有該類型的對象功能的描述。一個類由若干成員組成。每一個成員要么是數(shù)據(jù)成員(也叫做變量),要么是成員函數(shù)(也叫做方法)。與類同名的成員函數(shù)叫做構(gòu)造函數(shù)。構(gòu)造函數(shù)初始化類中的數(shù)據(jù)成員。其他成員函數(shù)操縱數(shù)據(jù)成員,并為其他類提供可復用的功能。
類的實例叫做對象。做個比喻,一個類可以看成是制作餅干的器具,那么對象就是該器具制作出來的餅干。當為對象建立引用后,這個對象可通過引用訪問。用new操作符實例化一個類,就是在堆中為實例或?qū)ο蠓峙浯鎯臻g存儲其成員信息。對于所有的面向?qū)ο笳Z言,用new操作符創(chuàng)建的對象,都從堆中分配存儲空間存儲類中定義的所有成員,如變量、常量、方法。
在C#中,類的靜態(tài)成員的訪問和Java一樣,用類名和“.”操作符。<className>.<memberName>
Console.WriteLine("HelloWorld!");
其中memberName是一個方法調(diào)用或者變量名。
同Java一樣,用“.”操作符也能進行引用對象成員的訪問。在C++中,如果引用是一個指針,那么使用指針運算符“->”。
<referenceName>.<memberName>
time.printStandard();
其中memberName是一個方法調(diào)用或者變量名。
C++提供了第二種方式來定義對象,即簡單實例化,不需要使用new操作符。這種方法不像堆中的引用類型,而像棧中的局部類型。在C#中,“new”關鍵字是創(chuàng)建對象實例的唯一方法。
類的語法可以通過下面的語法圖描述,方括號內(nèi)是可選項。
[attributes][modifiers]class<className>[:baseClassName]
{[classbody]
}[;]
其中,attributes被看做是內(nèi)聯(lián)注釋和聲明語句,這些語句可以添加到類、成員、參數(shù)或者其他編碼元素中。通過一個稱為reflection的庫,可以檢索這些附加的信息并在運行時供其他代碼使用。attributes提供了把信息和聲明相關連的通用方法,在許多情況下,它是一個強大的工具。
modifiers表示三種訪問控制符,分別是public,protected和private,它們與C++中的訪問控制符的含義相同。如果沒有明確定義訪問權(quán)限,C#和C++都默認為private。
還有一些其他的方法修飾符包括sealed、override、virtual,以及處理類的繼承和范圍的類修飾符abstract。
在C++中,程序員可以選擇在類聲明中定義類成員或者使用作用域操作符在類聲明外定義類成員。在C#中,所有類成員必須在類的大括號內(nèi)定義。將同一個類中相關聯(lián)的對象歸為一組的簡單思想可以設計出模塊化更好的代碼。
面向?qū)ο缶幊痰闹饕卣骶褪菍⒁粋€應用分解成多個類。其中的一個類包含Main()方法,而其他類包含可重用的類成員和方法。
讓我們考慮一個幫助人們準備旅游的程序,其中包括美元同當?shù)厮柝泿胖g的換算,將當?shù)販囟绒D(zhuǎn)化為華氏溫度。該程序被分解成四個類,如圖2.2所示。
圖2.2問題被分解成四個類下面給出了實現(xiàn)旅游準備工作的示例代碼。myCost類中給出了帶一個參數(shù)的構(gòu)造函數(shù)。由于參數(shù)被多個成員函數(shù)使用,通過構(gòu)造函數(shù)傳遞參數(shù)就會更有效率。當用new操作符實例化對象時,該參數(shù)值被傳給對象。而其他兩個類CurrencyConversion和TemperatureConversion則不需要構(gòu)造函數(shù)。在這些類中,參數(shù)僅僅被一個成員函數(shù)使用,因此,我們可以直接將參數(shù)傳遞給成員函數(shù),而不是建立數(shù)據(jù)成員保存參數(shù)值。usingSystem;
classTravelPreparation{//MainClass
staticvoidMain(string[]args){//Themainmethod
Console.WriteLine("Pleaseenterthenumberofdaysyouwilltravel");
Stringstr=Console.ReadLine();//readastringofcharacters
Int32daysToStay=Convert.ToInt32(str);//Convertstringtointeger
myCostusdObject=newmyCost(daysToStay);//Createanobject
intusdCash=usdObject.total();//Callamethodintheobject
Console.WriteLine("Pleaseenterthecountrynameyouwilltravelto");Stringcountry=Console.ReadLine();
CurrencyConversionexchange=newCurrencyConversion();
DoubleAmountLocal=exchange.usdToLocalCurrency(country,usdCash);
Console.WriteLine("Theamountoflocalcurrencyis:"+AmountLocal);
Console.WriteLine("PleaseenterthetemperatureinCelsius");
str=Console.ReadLine();
Int32celsius=Convert.ToInt32(str);TemperatureConversionc2f=newTemperatureConversion();
Int32fahrenheit=c2f.getFahrenheit(celsius);
Console.WriteLine("LocaltemperatureinFahrenheitis:"+fahrenheit);
}
}
classmyCost{
privateInt32days;//Datamember
publicmyCost(Int32daysToStay){//Parameterpassedintotheclassdays=daysToStay;//throughtheconstructor,whichis
}//usedtoinitializethedatamember
privateInt32hotel(){
return100*days;//Parametervalueusedinallmethods
}
privateInt32rentalCar(){
return30*days;//Parametervalueusedinallmethods
}privateInt32meals(){
return20*days;//Parametervalueusedinallmethods
}
publicInt32total(){
returnhotel()+rentalCar()+meals();
}
}
classCurrencyConversion{publicDoubleusdToLocalCurrency(Stringcountry,Int32usdAmount){
switch(country){
case"Japan":returnusdAmount*117;
case"EU":returnusdAmount*0.71;
case"HongKong":returnusdAmount*7.7;case"UK":returnusdAmount*0.49;
case"SouthAfrica":returnusdAmount*6.8;
default:return-1;
}
}
}
classTemperatureConversion{
publicInt32getFahrenheit(Int32c){
Doublef=c*9/5+32;returnConvert.ToInt32(f);
}publicInt32getCelsius(Int32f){
Doublec=(f-32)*5/9;returnConvert.ToInt32(c);
}
}
這個程序中使用的是自己編寫的類。在以后的章節(jié)學習面向服務的計算時,我們將展示通過Internet訪問遠程對象,即所謂的Web服務,它提供實時服務,如取得當?shù)氐臏囟群蛯嶋H匯率。2.1.6參數(shù):用ref和out傳遞引用
在C#中,通過引用傳遞參數(shù),或者被調(diào)用方法需要永久改變調(diào)用者的值,就要用ref關鍵字。看下面的例子:
usingSystem;
classPoint{
publicPoint(intx){
this.x=x;
}
publicvoidGetPoint(refintx){
x=this.x;//this.xreferstotheclassmember
}intx;
}
classTest{
publicstaticvoidMain(){
PointmyPoint=newPoint(10);intx=0;
myPoint.GetPoint(refx);//x=10
}
}
C#提供通過引用傳遞參數(shù)的第二種方法是用out關鍵字。out關鍵字可以傳遞未被引用初始化的參數(shù)。usingSystem;
classPoint{
publicPoint(intx){
this.x=x;
}
publicvoidGetPoint(outintx){
x=this.x;
}
intx;}
classTest{
publicstaticvoidMain(){
PointmyPoint=newPoint(10);
intx;
myPoint.GetPoint(outx);//x=10
}
}2.1.7基類和基類構(gòu)造函數(shù)調(diào)用
C#與C++定義父類的語法相似。類只能有一個父類。定義基類和調(diào)用基類的構(gòu)造函數(shù)的語法如下所示:
classCalculatorStack:stack{
publicCalculatorStack(intn):stack(n){
...//othercodehere
}
}2.1.8構(gòu)造函數(shù)、析構(gòu)函數(shù)和垃圾回收
與C++一樣,如果程序員沒有定義構(gòu)造函數(shù),C#會為每個類創(chuàng)建一個默認的構(gòu)造函數(shù)。這保證了類的成員變量被設置為適當默認值,而不是隨機指向某個位置。一個類可以定義多個構(gòu)造函數(shù)。構(gòu)造函數(shù)的語法包括public修飾符和帶有零個或多個參數(shù)的類名。當實例化對象時,自動調(diào)用構(gòu)造函數(shù),構(gòu)造函數(shù)無返回值。
通常,析構(gòu)函數(shù)釋放對象占用的存儲空間。在C++中,當不再使用對象時,由程序員負責執(zhí)行析構(gòu)函數(shù)釋放在堆中為對象分配的內(nèi)存空間。如果沒有人工清理,就會內(nèi)存泄露并可能最終導致系統(tǒng)崩潰。C#通過.Net垃圾回收器(GC)自動清理對象并跟蹤內(nèi)存分配,避免了這種潛在的問題。GC具有不確定性。它不會經(jīng)常運行去占用處理器的運行時間。它只在堆內(nèi)存不足時運行。在一些情況下,C#程序員想手動釋放資源,例如,當使用像數(shù)據(jù)庫連接或者窗口句柄這樣的非對象資源時。為了確保完成手動垃圾回收,Object.Finalize方法可以被重寫。C#中沒有delete操作符。除了這一微小的差別外,Object.Finalize方法具有和C++析構(gòu)函數(shù)相同的語法和作用,如下面代碼所示。
publicclassDestructorExample{
publicDestructorExample(){
Console.WriteLine(′Woohoo,objectinstantiated!′);
}~DestructorExample(){
Console.WriteLine(′Wow,destructorcalled!′);
}
}2.1.9C#中的指針
C#支持下列指針操作符,C++程序員對這些操作符已很熟悉:
(1)&:該地址運算符返回變量的內(nèi)存地址。
(2)*:基本的指針運算符,用于以下兩種情況:①聲明一個指針變量;
②間接引用,即訪問指針指向內(nèi)存位置的值。
(3)>成員訪問操作符,首先獲取指向?qū)ο蟮闹羔?然后獲得對象特定成員。
(*p).x;
p>x;
C/C++中指針的語義,以及引用和間接引用的語法,在C#中得到延續(xù)。C#的主要區(qū)別是任何使用指針的代碼都需要被標記為unsafe。當生明一個unsafe方法時,要添加關鍵字unsafe,以標記調(diào)用了unsafe方法的代碼塊。在unsafe中寫的程序并不是不安全的,它只是簡單的允許程序員直接操縱內(nèi)存,避免編譯器類型檢查。unsafe代碼不是不被管理的代碼,而仍然由實時環(huán)境和GC管理。
C#指針可以指向值類型(基本數(shù)據(jù)類型)或者引用類型。但是,你只能看到值的地址。另一個需要注意的是,如果你使用VisualStudio.Net編程,代碼需要使用unsafe編譯選項進行編譯。
這個例子說明了C#指針的用法:
publicclassMyPointerTest{
unsafepublicstaticvoidSwap(int*xVal,int*yVal){
inttemp=*xVal;
*xVal=*yVal;*yVal=temp;
}
publicstaticvoidMain(string[]args){
intx=5;
inty=6;
Console.WriteLine("OriginalValue:x={0},y={1}",x,y);
unsafe{
Swap(&x,&y);
}Console.WriteLine("NewValue:x={0},y={1}",x,y);
}
}
控制臺輸出為:
OriginalValue:x=5,y=6
NewValue:x=6,y=52.1.10C#的統(tǒng)一類型系統(tǒng)
C#的統(tǒng)一類型系統(tǒng)使每一個數(shù)據(jù)類型的值都成為一個對象。引用類型(復雜類型)和值類型擁有相同的根類System.Object。通過這種方式,值類型繼承了根類System.Object中的方法。下面是C#代碼示例:
5.ToString()//Retrievesthenameofanobject
b.Equals()==c.Equals()//Comparestwoobjectreferencesatruntime
w.GetHashCode()//Getsthehashcodeforanobject
4.GetType()//Getsthetypeofanobject
因為所有的類型都從object派生,所以值類型可以使用點(.)操作,而不需要為值類型單獨定義一個類。這樣當需要像引用類型那樣使用值類型時,就不必像面向?qū)ο蟪绦騿T用C++(Java)編程時那樣必須寫一些代碼以把值類型包裝成一個類。
在C++中,如果你想創(chuàng)建一個接受任何類型的帶參數(shù)的方法,你必須寫一個帶重載構(gòu)造函數(shù)的類以支持你需要的每一個值類型。例如:
classAllTypes{public:
AllTypes(intw);
AllTypes(doublex);
AllTypes(chary);
AllTypes(shortz);
//aconstructormustbeoverloadedforeachdesiredtype
//retrievingavaluefromthisclasswouldrequireoverloadedfunctions};
classCTypesExample
{publicExample(AllTypes&myType){
}
};
在C#中,當需要引用類型卻提供的是值類型時,編譯器會自動對值類型裝箱,并在堆中分配內(nèi)存。裝箱(Boxing)是編譯器將一個值類型轉(zhuǎn)換為引用類型的過程。反裝箱(Unboxing)是將引用類型轉(zhuǎn)回為值類型。
Boxing和Unboxing實例一:
intv=55;
Console.WriteLine("Valueis:{v}",v);
//Console.Writelineacceptsobjects/referencesonly
//Thecompilerwrapsvaluetypesautomatically
intv2=(int)v;//Unboxingneedscasting
Boxing和Unboxing實例二:
intv=55;
objectx=v;//explicitlyboxintvaluetypevintoreferencetypex
Console.WriteLine("Valueis:{0}",x);//Console.Writelineacceptsobjects
裝箱一個變量實際上是生成一個不同的變量。如果你修改了原來的變量,被裝箱的變量不會被修改。如下面的代碼示例所示:
classtestBox{staticvoidMain(string[]args){
intva1=55;
objectbox=va1;//boxintvalueva1intoreferencetype
va1=va1*2;//modifytheoriginalvariable
Console.WriteLine("va1valueis:{0}",va1);//va1=55
intva2=(int)box;//unboxtheobjectandputintova2
Console.WriteLine("va2valueis:{0}",va2);//va2-110
//Adifferentvaluewillbeprinted
//intva3=box.va1;isINCORRECT:va1isNOTamemberofbox}
}
統(tǒng)一類型系統(tǒng)使得跨語言互操作成為可能。類型系統(tǒng)的其他優(yōu)點包括保證類型安全,即在運行時追蹤系統(tǒng)中的所有類型的一種安全機制。最終結(jié)果是通過這種在概念上建立一個更簡單的編程模型的思想,使代碼更安全。
當一個程序啟動時,操作系統(tǒng)將會給程序分配一個內(nèi)存段。編程語言環(huán)境(運行系統(tǒng))管理分配給程序的內(nèi)存。分配的內(nèi)存分為三個區(qū)域:靜態(tài)區(qū)、棧區(qū)、堆區(qū),如圖2.3所示。
程序員可以通過不同的方式聲明變量來選擇在不同的區(qū)獲得內(nèi)存。在C#和Java中:2.2內(nèi)存管理和垃圾回收
(1)所有靜態(tài)變量從靜態(tài)區(qū)獲取內(nèi)存。換句話說,如果想為變量在靜態(tài)區(qū)域獲得內(nèi)存,可以在變量聲明前加上static,例如:
staticints聲明了一個靜態(tài)的整型變量。在C++中,允許使用全局變量,無論static限定是否被使用,所有全局變量(在函數(shù)外聲明的變量)都可以從靜態(tài)區(qū)獲得內(nèi)存。
(2)通過new()創(chuàng)建一個對象時,該對象動態(tài)從堆中獲得內(nèi)存。
(3)方法中的所有非靜態(tài)局部變量從棧中獲得內(nèi)存。這個棧也被叫做程序運行時棧,以區(qū)別計算機系統(tǒng)中可能使用的其他棧。
圖2.3分配給程序的內(nèi)存的分區(qū)
現(xiàn)在的問題是,程序員使用不同的存儲區(qū)有什么不同?這個問題將在下面一節(jié)給出答案。2.2.1靜態(tài)變量和靜態(tài)方法
考慮類中的靜態(tài)變量。內(nèi)存在編譯階段(程序執(zhí)行之前)分配給靜態(tài)變量。無論從類創(chuàng)建多少個對象,每個靜態(tài)變量都只有一個內(nèi)存拷貝。在一個對象中對靜態(tài)變量做出改變將改變其他對象中這個變量的值。僅僅當這個程序結(jié)束時,靜態(tài)變量的生命期才結(jié)束。
我們?yōu)槭裁葱枰o態(tài)局部變量?我們需要靜態(tài)變量來保存被不同對象所共享的值。例如,我們可以定義一個靜態(tài)變量“counter”統(tǒng)計一個資源被不同的對象訪問多少次。下面的函數(shù)是一個用靜態(tài)變量統(tǒng)計有多少用戶成功登陸了一個訪問受限區(qū)域的例子。
voidlogin(){
staticintcounter=0;//willbeinitializedonlyonce
readId_pwd();
if(verified())
counter++;//countthe#ofusersloggedin
}
我們可以聲明一個靜態(tài)方法。不需要創(chuàng)建包含靜態(tài)方法的對象就可以調(diào)用靜態(tài)方法,而非靜態(tài)方法僅僅是在對象被創(chuàng)建后才可以被調(diào)用。靜態(tài)方法通過className.methodName來調(diào)用,而非通過referenceName.methodName來調(diào)用。2.2.2局部變量的運行時棧
局部變量是在方法中聲明的變量。當控制進入一個方法,就在棧中創(chuàng)建一個內(nèi)存塊(叫做棧區(qū))。所有非靜態(tài)局部變量從棧區(qū)獲得內(nèi)存。當控制離開方法,所有局部變量被釋放,并且這些變量的內(nèi)容不再有效(不再可以訪問)。
圖2.4給出了棧內(nèi)存分配的示例。如圖2.4左邊部分所示,這個程序包含兩個方法。main方法有一個局部變量i,bar方法有兩個局部變量j和k。請注意,方法的形參是這個方法的局部變量。
圖2.4一個簡單程序及它的運行時棧
狀態(tài)(0)顯示了main方法執(zhí)行前棧的初始狀態(tài)。當控制進入main方法,局部變量i在棧頂部獲得內(nèi)存,如棧狀態(tài)(1)所示。i的值初始化為0,然后增至1。然后i作為實參傳遞給bar方法,當控制進入bar方法,兩個局部變量j和k獲得棧頂部的內(nèi)存,如狀態(tài)(2)所示。i的值傳遞給形參j。請注意,i和j有不同的存儲單元。j有i值的副本。當j在bar方法中被修改時,不影響變量i的值。
當bar函數(shù)運行完后,變量j和k生命期結(jié)束。棧指針返回bar函數(shù)運行之前的位置。變量j和k釋放它們使用的內(nèi)存,如棧狀態(tài)(3)所示。因此我們無法在方法外訪問j和k。最后,當main函數(shù)運行結(jié)束,變量i也被釋放,棧指針返回到main函數(shù)運行前的位置。如圖2.4狀態(tài)(4)所示。
由于局部變量在它生命期結(jié)束時由運行時棧自動回收垃圾,因此程序員不需要明確地向系統(tǒng)返回內(nèi)存。了解了如何給局部變量在棧中分配內(nèi)存后,我們就很容易理解遞歸方法的實現(xiàn)。實際上,并不需要任何特殊機制。棧處理所有局部變量,也處理遞歸方法的變量。
讓我們看看下面的fac(n)遞歸函數(shù)。函數(shù)中有兩個局部變量:形參n和一個用來保存第(n-1)次迭代返回值的臨時變量fac。圖2.5顯示了fac(3)遞歸函數(shù)執(zhí)行前和執(zhí)行中的運行時棧。
usingSystem;
namespacemyNamespace1
{classstackExample{
staticvoidMain(string[]args){
inti=3,j;
j=fac(i);
Console.WriteLine("j={0}",j);
}
staticintfac(intn){
if(n<=1)
return1;
elsereturnn*fac(n-1);
}
}
}
圖2.5遞歸程序的運行時棧
狀態(tài)(0)是fac(3)函數(shù)被調(diào)用前的狀態(tài)。當fac(n)函數(shù)第一次執(zhí)行時,兩個局部變量n和fac從棧上獲得內(nèi)存,如狀態(tài)(1)所示。形參n被初始化為實際的參數(shù)3,但是變量fac還沒有值。第一次迭代中,fac(n-1)被調(diào)用,并且函數(shù)重新執(zhí)行。兩個局部變量再次從棧獲得內(nèi)存?,F(xiàn)在,n被初始化為實際參數(shù)2,而fac沒有值,如圖2.5狀態(tài)(2)。第二次迭代中的變量n和fac不同于第一次迭代中的n和fac,雖然它們有相同的名字,但由于它們有不同的域,所以被認為是不同的變量。
在第二次迭代中,fac(n-1)被再次調(diào)用。在這次迭代中,n被初始化為1,條件(n≤1)為真?,F(xiàn)在fac(1)函數(shù)實際已經(jīng)完成在這次迭代中把一個值返回給fac,如狀態(tài)(3)所示。迭代3完成的函數(shù)在返回時調(diào)用迭代2中的fac(1),并且返回值fac=1傳入迭代2。操作n*fac(1)產(chǎn)生一個值2,如狀態(tài)(4)所示。返回值2被傳給迭代1并產(chǎn)生一個值6,如圖2.5狀態(tài)(5)所示。當最后迭代完成時,fac(n)函數(shù)退出,棧指針返回初始狀態(tài)(0)。
如果把這里遞歸方法的調(diào)用和前面例子中普通方法的調(diào)用相比較,你會發(fā)現(xiàn)在棧中為變量分配內(nèi)存的過程是相同的。
實際上,在匯編語言(或機器代碼)中,用來保存返回值的fac并不在棧中。相反地,它用的是寄存器。一個寄存器變量可被認為是一個全局變量,它對高級語言編程者是不可見的。因為寄存器概念不是高級語言編程的一部分,所以我們在這兒用棧變量fac使得值傳遞在棧中可見。2.2.3動態(tài)存儲分配的堆
數(shù)據(jù)塊的第三個區(qū)域是堆。在Java和C#中用newClassname()操作時,要求從堆中進行動態(tài)內(nèi)存分配。
例如下面的代碼段要求給Invoice類的對象分配內(nèi)存:
classInvoice{//defineaclass
floatprice;
intphone;
};
Invoicep=newInvoice();
從堆中獲得內(nèi)存的數(shù)據(jù)類型叫做引用類型,因為他們的變量用內(nèi)存地址(引用)作為值。2.2.4作用域和垃圾回收
到目前為止,我們解釋了什么時候使用靜態(tài)內(nèi)存、棧內(nèi)存和堆內(nèi)存,也解釋了怎樣從靜態(tài)區(qū)、棧區(qū)和堆區(qū)獲得內(nèi)存。最后一個需要回答的問題是,我們是否需要擔心垃圾回收?換句話說,我們是否需要回收已分配過的內(nèi)存?這個問題的答案取決于我們獲得內(nèi)存的地方和所使用的語言。
根據(jù)定義,靜態(tài)變量應該存在于程序的整個生命周期(盡管它們不可見),因此永遠不需要程序員或者運行時系統(tǒng)進行垃圾回收。當main函數(shù)運行結(jié)束,操作系統(tǒng)將回收所有分配給該程序的內(nèi)存段。因此,如果一個變量或函數(shù)是靜態(tài)的,我們就不需要擔心內(nèi)存的回收。
如果一個變量從棧獲得內(nèi)存,內(nèi)存將被系統(tǒng)自動回收。如我們前面解釋過的,當函數(shù)開始運行,局部變量或?qū)ο髲臈V蝎@得內(nèi)存。當函數(shù)運行結(jié)束,棧指針移回原來的位置,分配給局部變量的內(nèi)存返回給棧。這種內(nèi)存回收由語言的作用域規(guī)則管理:變量的作用域從聲明開始,結(jié)束于塊尾。當變量超出作用域,分配給變量的內(nèi)存返還給系統(tǒng)。
然而,如果變量或?qū)ο髲亩阎蝎@得內(nèi)存,就會變得很復雜。Java使用一個復雜的自動垃圾回收器回收不再用的內(nèi)存。C#對管理的代碼使用自動垃圾回收器。對于違背管理的代碼,需要手動(顯式)回收堆內(nèi)存。在C++中,所有內(nèi)存都要手動回收。
表2.4內(nèi)存管理匯總
本節(jié)討論多任務和多線程的一般問題,包括并行處理、同步、死鎖、執(zhí)行次序。這些內(nèi)容是分布式計算范型的基礎。2.3多任務和多線程的一般問題2.3.1基本需求
操作系統(tǒng)中的多任務和應用程序中的多線程類似,提供同時執(zhí)行代碼不同部分的能力,同時保持計算結(jié)果的正確。
在操作系統(tǒng)中,并行執(zhí)行的代碼稱為進程或任務,并且它們在語義上往往相互獨立(但可以有關)。操作系統(tǒng)進行進程調(diào)度和資源(處理器、內(nèi)存、外圍設備等)分配。操作系統(tǒng)允許用戶創(chuàng)建、管理和同步化進程。在應用程序中,并行執(zhí)行的代碼叫做線程。更多的時候它們是語義相關的(但也可以獨立)。
在這兩種情況下,程序員必須仔細設計操作系統(tǒng)/應用程序以達到進程/線程同時運行而又不相互干擾,并且不管它們以什么順序執(zhí)行,在最后都輸出相同結(jié)果。
我們區(qū)分程序和進程以及函數(shù)(方法)和線程。一個程序或函數(shù)(方法)是由程序員寫的一段代碼,它是靜態(tài)的。進程或線程是由執(zhí)行的程序/函數(shù)、當前值、狀態(tài)信息和用于支持它執(zhí)行的資源構(gòu)成,資源是它執(zhí)行時的動態(tài)因素。換言之,一個進程或線程是一個動態(tài)實體,只有當程序或函數(shù)執(zhí)行時才會存在。
為了真正并行執(zhí)行多個進程/線程,必須存在多個處理器。如果系統(tǒng)中只有一個處理器,表面上多個進程/線程同時執(zhí)行,實際上是在分時模式下順序執(zhí)行。
從同一代碼塊可以創(chuàng)建多個進程/線程。默認情況下,包含在不同進程/線程中的代碼和數(shù)據(jù)是分離的,每一個都有它自己可執(zhí)行代碼的副本、局部變量的棧、對象數(shù)據(jù)區(qū)以及其他數(shù)據(jù)元素。
通常情況下,一個分布式操作系統(tǒng)可以由不同電腦上的多個實例或副本構(gòu)成,每一個實例或副本都可以管理多個進程。同樣,每一個進程可以是由多個線程組成的一個多線程程序,如圖2.6所示。
圖2.6多進程和多線程2.3.2臨界操作和同步
進程/線程可以共享資源或?qū)ο?。對共享資源的訪問稱為臨界操作,需要仔細的管理和同步化這些操作以預防出錯,例如,同步讀寫,這可能導致不正確或不確定的結(jié)果。同步化的一個簡單方式是訪問資源之前加鎖。圖2.7顯示了一個場景,兩個旅行社看到了一個飛機上的同一個位子,缺乏同步導致重復預定。解決這個問題的一個簡單方法是預定對象(座位)前鎖定,這樣其他旅行社在對象被鎖定時就不能進行訪問。雖然一個簡單的鎖定可以防止共享資源被同時訪問,但也消除了并行處理的可能性。更理想的方法是不鎖定并行讀操作,而鎖定并行讀—寫和寫—寫組合。
圖2.7缺乏同步導致重復預定
圖2.8顯示了一個更復雜的例子,一個生產(chǎn)者和兩個消費者線程共享一個有多個單元格的緩沖區(qū)。生產(chǎn)者不斷將產(chǎn)品放入緩沖區(qū)直到緩沖區(qū)滿,同時消費者不斷從緩沖區(qū)獲得產(chǎn)品直到緩沖區(qū)為空。我們怎樣寫一個程序模擬這個例子,使生產(chǎn)者和消費者并行讀寫緩沖區(qū),而不引起同步問題。
圖2.8生產(chǎn)者和消費者問題的一種解決方案我們可以用三個線程來模擬生產(chǎn)者和消費者,并用一個全局(靜態(tài))數(shù)組(例如整型)模擬有n個單元的緩沖區(qū),如圖2.8所示。偽代碼顯示了這個問題的可能的解決方案。
偽代碼是否正確解決了這個問題?代碼使用了一個共享變量counter,來確保當緩沖區(qū)滿時(counter=N)生產(chǎn)者不能把產(chǎn)品放進緩沖區(qū),當緩沖區(qū)為空時(counter=0)消費者不能從緩沖區(qū)取產(chǎn)品。這實際上是生產(chǎn)者—消費者問題的邏輯,它并沒有解決可能發(fā)生在分布式計算系統(tǒng)中的同步問題。讓我們看看下面的情形:
(1)counter值是5,生產(chǎn)者線程正在執(zhí)行語句counter=counter+1;,它獲得counter值,并且讓counter加1使其為6。
(2)在新值被寫入變量counter之前,生產(chǎn)者線程可能被下列原因之一打斷:數(shù)值(時間片)已用完;一個更高優(yōu)先級的進程到達,操作系統(tǒng)將處理器分配給新來的進程;發(fā)生一個異常,處理器必須處理異常。
(3)消費者線程得到了處理器,它將counter值從5減少到4。
(4)生產(chǎn)者線程重新獲得處理器,它從被打斷的點開始執(zhí)行——將值6賦給counter——不正確的值賦給了counter。
讓我們看看另一種情況:
(1)counter值為1,消費者線程A從緩沖區(qū)取出唯一產(chǎn)品。
(2)在消費者A執(zhí)行“counter=counter-1”之前,線程被打斷,并且消費者線程B獲得處理器。它仍然看見counter值為1,因此它從緩沖區(qū)提取不存在的一個產(chǎn)品。事實上,緩沖區(qū)只有一個值,而這個值被讀了兩次,這是一個邏輯錯誤。
為了正確的實現(xiàn)多線程,我們需要恰當?shù)氖褂谜Z言提供的同步機制,這將在本章其余部分做詳細討論。2.3.3死鎖和死鎖的解決
分布式計算的另一個重要問題是死鎖問題。死鎖的情況是兩個或多個競爭操作等待對方完成,導致都不能完成。一個典型的情況是:兩個或多個線程執(zhí)行時需要多個資源,每個線程都占有一個資源等待其他線程釋放資源,如圖2.9所示。
圖2.9死鎖場景
死鎖的經(jīng)典問題是哲學家就餐問題:五個哲學家都在思考和就餐。他們共享一個圓桌上的五個碗和五根筷子。思考是獨立的。就餐的時候他們使用與他相鄰兩人共享的筷子。限定每個哲學家一次只能拿起一根筷子,如圖2.10所示。
有三種技術可用來解決死鎖問題:
(1)死鎖預防:使用一種算法可以保證不會發(fā)生死鎖。
(2)死鎖避免:使用一種算法,能夠預見死鎖的發(fā)生從而拒絕資源請求。
(3)死鎖檢測和恢復:用一種算法來檢測死鎖的發(fā)生,強迫線程釋放資源、掛起等待。
圖2.10哲學家就餐問題
死鎖還不是唯一問題,活鎖和饑餓是兩個可能發(fā)生的相關問題。
活鎖是由于兩個或多個線程在響應其他線程改變的狀態(tài)下不斷改變它們的狀態(tài)而引起的。其結(jié)果是線程都不會獲得資源來完成它們的任務。例如,兩個線程試圖同時加鎖,在鎖打開時,它們的睡眠時間相同,醒來后又同時試圖加鎖。一個類似情況是當兩個人在走廊上相遇,兩個人都靠邊試圖讓另外一個人通過,但他們從一邊到另一邊搖擺不定,當他們試圖通過時又相互擋住了對方的路。和死鎖不同,死鎖中的資源被占有,而活鎖中資源是空閑的。為了避免活鎖,應使用不同的等待時間。例如,以太網(wǎng)使用的CSMA/CD協(xié)議采用二進制指數(shù)退讓算法來避免活鎖:
(1)發(fā)送前,以太網(wǎng)節(jié)點偵聽總線,如果總線空閑則發(fā)送。
(2)如果沖突發(fā)生,等待一個隨機時間:
①第一次沖突,等待0或1時間間隙。
②第二次沖突,等待0,1,2或3時間間隙。
③第三次沖突,等待0,1,2,3,4,5,6或7時間間隙。
④第四次沖突,等待0,1,2…或15時間間隙。
…
⑤第n次沖突,等待0,1,2…或2n-1時間間隙。
饑餓是指一個線程在理論上能夠訪問到的共享資源(加鎖),但實際上無法正常訪問并且不能往前推進。這種情況更可能發(fā)生,當線程給了優(yōu)先級,而有很多更高優(yōu)先級的線程時,低優(yōu)先級線程可能會餓死。其中一種解決方法是動態(tài)改變線程優(yōu)先級。線程等待時間越長優(yōu)先級就會變得越高。2.3.4執(zhí)行順序
線程可以代表不同的執(zhí)行者,我們可能有不同的需求,比如線程的不同執(zhí)行順序。同步化能預防多個線程同時訪問相同資源,但是不關心哪個線程首先訪問或訪問的順序是什么。例如,我們可以定義一個順序:在消費者從緩沖區(qū)取得產(chǎn)品之前生產(chǎn)者必須填滿緩沖區(qū)。我們也可以讓兩個消費者輪流從緩沖區(qū)獲得產(chǎn)品。
考慮另一個例子,我們要編寫一個多線程程序來模擬乒乓球比賽。我們需要定義執(zhí)行順序,例如,A1B2A2B1A1…B1,如圖2.11所示。
圖2.11定義執(zhí)行順序
協(xié)調(diào)線程的執(zhí)行順序需要一個不同的機制,我們在后面的小節(jié)將進一步討論這個問題并給出示例程序。2.3.5操作系統(tǒng)對多任務和多線程的支持
大多數(shù)操作系統(tǒng)支持多任務編程。例如,Unix系統(tǒng)為用戶進行并行處理提供了幾個系統(tǒng)調(diào)用:
intfork()
是unix為創(chuàng)建新進程提供的系統(tǒng)調(diào)用。當一個進程執(zhí)行了fork(),就創(chuàng)建了一個新(子)進程,這個進程基本上是父進程的拷貝:具有相同的程序代碼,包括fork()語句、狀態(tài)、用戶數(shù)據(jù)、系統(tǒng)數(shù)據(jù)段都被復制。唯一的不同是這兩個進程(父和子)從系統(tǒng)調(diào)用fork()中返回不同的值:子進程返回值為0,而父進程的返回值是子進程的過程ID。如果父進程返回值是-1,就說明在創(chuàng)建子進程時產(chǎn)生了錯誤。fork()沒有參數(shù),調(diào)用者不會做錯什么。錯誤的唯一原因是資源耗盡(例如內(nèi)存不足)。在異常處理中,父進程可能需要等待一段時間(用睡眠調(diào)用)稍后再試一次。
另外在Unix系統(tǒng)中,fork()經(jīng)常和exec(參數(shù)列表)一起使用。通常情況下,子進程返回fork()調(diào)用后會執(zhí)行exec(參數(shù)列表),而父進程等待子進程終止或做其他的事情。圖2.12是一個使用fork()和exec(參數(shù)列表)的典型應用。
圖2.12系統(tǒng)調(diào)用fork()和exec()的典型應用不同版本的系統(tǒng)調(diào)用支持不同的參數(shù)格式。下面是系統(tǒng)調(diào)用的兩個版本的C語言描述。
//Version1:listallparameters
intexecl(path,arg0,arg1,...,argn,null)
char*path;//path(location)ofprogramfile
char*arg0;//firstargument(programfile)
char*arg1;//secondargument
...
char*argn;//lastargumentchar*null;//nullindicatesendofarguments
//Version2:Useafilenameandanarrayastheparametersintexecvp(file,argv)
char*file;//programfilename
char*argv();//pointertothearrayofarguments
下面的C程序是一個命令行解釋器(CLI)的簡單設計,它等待輸入一個命令,然后開始執(zhí)行與命令相關的程序并把它作為自己的子進程。
#include<fcntl.h>//CommandLineInterpreter
staticvoidmain(intargc,char*argv())
{while(TRUE)
{//read,executeacommandandwaitfortermination
read_command(argv);
//readcommandnameinargv[0]anddatainargv[1]...argv[argc–1]
switch(fork())
{case–1:
printf("Cannotcreatenewprocess\n");
break;
case0:
execvp(argv[0],argv);
//Theexecvpfunctionshouldneverreturn.Ifitreturns
printf("Cannotexecute\n");//anerrormusthaveoccurred
break;
default://CLIprocessitselfwillcometothiscase
if(wait(NULL)==-1)printf("Cannotexecutewaitsystemcall\n");
//ParentprocessreceivesthePIDofchildprocessandthenwaitsfortheterminationofchild
}
}
}
這部分討論在Java中創(chuàng)建和管理線程,以及線程之間的通信和同步。這部分的程序可以使用J#,.Net,Eclipse以及其他的Java編程環(huán)境執(zhí)行。2.4Java中的多線程2.4.1創(chuàng)建和啟動線程
Java編程環(huán)境通過Thread類支持多線程編程,這個類在Java標準類庫中定義,它的方法有:start,run,wait,sleep,notify,notifyAll等。圖2.13顯示了由這些方法、事件以及系統(tǒng)函數(shù)如分發(fā)、超時、訪問一個已鎖對象變?yōu)樽枞麘B(tài)、對象解鎖以后變?yōu)槲醋枞约巴瓿伤刂频木€程狀態(tài)轉(zhuǎn)換。
圖2.13Java線程狀態(tài)轉(zhuǎn)換圖
Java提供兩種方式創(chuàng)建一個新線程:繼承Thread類或?qū)崿F(xiàn)Runnable接口。
通過繼承Thread類創(chuàng)建新線程的步驟如下:
(1)繼承Thread類;
(2)覆蓋run()方法;
(3)用newMyThread(...)創(chuàng)建一個線程;
(4)通過調(diào)用start()方法啟動線程。
通過實現(xiàn)Runnable接口創(chuàng)建新線程的步驟如下:
(1)實現(xiàn)Runnable接口;
(2)覆蓋run()方法;
(3)用newMyThread(...)創(chuàng)建一個線程;
(4)通過調(diào)用start()方法啟動線程;
通過第一種方法創(chuàng)建線程,你需要定義一個Thread類的子類,在子類中必須定義一個run()方法作為線程的入口點,就像main()作為程序的入口點一樣。你也可以定義多個類,每個類都有一個run()方法,但是你只能定義一個含有main()的類。下面的程序給出了一個例子,其中定義了兩個線程類和一個main類。在main類中,每一個Thread類創(chuàng)建兩個線程。線程的創(chuàng)建類似于對象的創(chuàng)建。當Thread類中的start()方法被調(diào)用后一個線程開始執(zhí)行。創(chuàng)建和啟動線程的過程類似于操作系統(tǒng)中進程的創(chuàng)建和啟動,這些我們在前面章節(jié)中已經(jīng)討論過。classmyThread1extendsThread{
//othermembers
publicvoidrun(){
//dosomethinginthisthread
}
}
classmyThread2extendsThread{
//othermembers
publicvoidrun(){
//dosomethingelseinthisthread
}
}publicclassTestMyThreads{//mainclass
publicstaticvoidmain(Stringargs[]){
myThread1threadA=newmyThread1();//creatingathread
myThread1threadB=newmyThread1();
myThread2threadC=newmyThread2();
myThread2threadD=newmyThread2();
threadA.start();//startingathread
threadB.start();
threadC.start();
threadD.start();
}}
通過繼承Thread類創(chuàng)建線程有一個限制。因為Java不支持多繼承,也就是,Java類只能有一個基類,如果類已經(jīng)繼承了一個類,那就不能再繼承Thread類。
在這種情況下,你可以聲明一個實現(xiàn)Runnable接口的類。然后在這個類中,編寫一個run()方法實現(xiàn)線程的功能。當創(chuàng)建和啟動一個線程后,創(chuàng)建一個類的實例并作為參數(shù)傳遞。下面的程序給出了一個例子,這個例子對繼承Thread類和實現(xiàn)Runnable接口這兩種方式進行了說明。classmyThread1extendsThread{//UsingextendsThread
intmyID;
myThread1(intid){//constructor
myID=id;
}
publicvoidrun(){
for(inti=1;i<5;i++){
intsecond=(int)(Math.random()*500);
try{
Thread.sleep(second);
}catch(InterruptedExceptione){}
System.out.println("myThread1-id"+myID+":"+i);
}
}
}//endclassmyThread1
classmyThread2implementsRunnable{//UsingimplementsRunnable
intmyID;myThread2(intid){
myID=id;
}
publicvoidrun(){
for(intj=1;j<5;j++){
try{
Thread.sleep((int)(Math.random()*500));
}
catch(InterruptedExceptione){}
System.out.println("myThread2-id"+myID+":"+j*j);
}}
}//endclassmyThread2
publicclasstestMyThreads{
publicstaticvoidmain(String[]args){
Threadt1=newmyThread1(1);//extendsThread
Threadt2=newThread(newmyThread1(2));//implementsRunnable
Threadt3=newThread(newmyThread2(3));//implementsRunnable
Threadt4=newThread(newmyThread2(4));//implementsRunnable
t1.start();t2.start();
t3.start();
t4.start();
}
}//endtestMyThreads
從上面兩個例子,我們看到,可以創(chuàng)建并啟動一個線程的多個實例。這是線程的一個常見應用。例如,你可以定義生產(chǎn)者線程和消費者線程。然后,可以創(chuàng)建具有多個生產(chǎn)者和消費者的生產(chǎn)者—消費者問題。從一個線程類創(chuàng)建多個線程被稱為生產(chǎn)線程。
可以設置線程優(yōu)先級,分別用數(shù)字1~10表示。在Thread類中定義了三個常量(宏):MIN_PRIORITY,NORM_PRIORITY和MAX_PRIORITY,相應的優(yōu)先級分別是1,5和10。默認情況下(不設置優(yōu)先級),一個線程的優(yōu)先級是NORM_PRIORITY。如果所有線程的優(yōu)先級相同,在等待隊列、阻塞隊列和就緒隊列中,它們將以先進先出的方式被服務。如果線程的優(yōu)先級不同,高優(yōu)先級的線程將排在低優(yōu)先級線程的前面,而不論其到達時間的先后。
下面代碼黑體部分顯示了在四個線程啟動之前對其設置不同的優(yōu)先級。在線程執(zhí)行過程中,打印出優(yōu)先級。
classmyThread1extendsThread{//UsingextendsThread
intmyID;
myThread1(intid){//constructor
myID=id;
}
publicvoidrun(){
for(inti=1;i<5;i++){
intsecond=(int)(Math.random()*500);try{Thread.sleep(second);}
catch(InterruptedExceptione){}
System.out.print("myThread1-id"+myID+":"+i);
System.out.println("mypriorityis"+getPriority());
}
}
}//endclassmyThread1
classmyThread2extendsThread{//UsingextendsThread
intmyID;
myThread2(intid){myID=id;
}
publicvoidrun(){
for(intj=1;j<5;j++){
try{
Thread.sleep((int)(Math.random()*500));
}
catch(InterruptedExceptione){}
System.out.print("myThread2-id"
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 購房借款合同書樣式
- 寄售合同模板示例
- 計算機軟件技術外包合同詳解
- 內(nèi)控管理咨詢合同案例
- 版物業(yè)服務合同協(xié)議書全文示例
- 私人金融顧問服務協(xié)議
- 寶雞市房屋買賣合同稅費計算器
- 電子版的分包勞動合同
- 簡單借款協(xié)議書解讀
- 小型構(gòu)件的勞務分包合同
- LSS-250B 純水冷卻器說明書
- 中藥分類大全
- 防止返貧監(jiān)測工作開展情況總結(jié)范文
- 精文減會經(jīng)驗交流材料
- 2015年度設備預防性維護計劃表
- 淺談離子交換樹脂在精制糖行業(yè)中的應用
- 設備研發(fā)項目進度表
- 管道定額價目表
- 新時期如何做好檔案管理課件
- 復興號動車組空調(diào)系統(tǒng)設計優(yōu)化及應用
- 礦山壓力與巖層控制課程設計.doc
評論
0/150
提交評論