第五章線(xiàn)程與異常處理(上)
上一章我們介紹了Java的面向對象的機制:類(lèi)、包和接口。本章我們將介紹一下Java的另外兩個(gè)機制:多線(xiàn)程(Multithread)和異常處理(Exception)。本章前半部分是關(guān)于Thread這一基本類(lèi)以及一套先進(jìn)的同步原語(yǔ)的介紹,它們使得利用Java編寫(xiě)多線(xiàn)程大為方便。在本章的后半部分我們將介紹Java的異常處理機制(Exception),異常處理機制提高了程序的健壯性。另外,本章中間將介紹一個(gè)Java的debugger工具Jdb的使用,Jdb工具對于調試多線(xiàn)程程序尤其有好處。
5.1 多線(xiàn)程(Multithread)
5.1.1 線(xiàn)程的基本概念
在介紹多線(xiàn)程之前,我們先來(lái)了解一些相關(guān)的基本概念。一般來(lái)說(shuō),我們把程序的一次執行稱(chēng)為進(jìn)程(process)。一個(gè)進(jìn)程包括一個(gè)程序模塊和該模塊一次執行時(shí)所處理的數據。每個(gè)進(jìn)程與其它進(jìn)程擁有不同的數據塊,其內存地址是分開(kāi)的。進(jìn)程之間的通信要通過(guò)尋址,一般需使用信號、管道等進(jìn)行通信。線(xiàn)程(thread)是指進(jìn)程內部一段可獨立執行的有獨立控制流的指令序列。子線(xiàn)程與其父線(xiàn)程共享一個(gè)地址空間,同一個(gè)任務(wù)中的不同線(xiàn)程共享任務(wù)的各項資源。
多進(jìn)程與多線(xiàn)程是多任務(wù)的兩種類(lèi)型。以前的操作系統,如Win31,只運行多進(jìn)程,而Win95及WinNT則支持多線(xiàn)程與多進(jìn)程。Java通過(guò)提供Package類(lèi)(Java.lang.package)支持多進(jìn)程,而提供Thread類(lèi)來(lái)支持多線(xiàn)程。
多線(xiàn)程與多進(jìn)程的主要區別在于,線(xiàn)程是一個(gè)進(jìn)程中一段獨立的控制流,一個(gè)進(jìn)程可以擁有若干個(gè)線(xiàn)程。在多進(jìn)程設計中各個(gè)進(jìn)程之間的數據塊是相互獨立的,一般彼此不影響,要通過(guò)信號、管道等進(jìn)行交流。而在多線(xiàn)程設計中,各個(gè)線(xiàn)程不一定獨立,同一任務(wù)中的各個(gè)線(xiàn)程共享程序段、數據段等資源,如圖5.1。
正如字面上所表述的那樣,多線(xiàn)程就是同時(shí)有多個(gè)線(xiàn)程在執行。在多CPU的計算機中,多線(xiàn)程的實(shí)現是真正的物理上的同時(shí)執行。而對于單CPU的計算機而言,實(shí)現的只是邏輯上的同時(shí)執行。在每個(gè)時(shí)刻,真正執行的只有一個(gè)線(xiàn)程,由操作系統進(jìn)行線(xiàn)程管理調度,但由于CPU的速度很快,讓人感到像是多個(gè)線(xiàn)程在同時(shí)執行。
多線(xiàn)程比多進(jìn)程更方便于共享資源,而Java又提供了一套先進(jìn)的同步原語(yǔ)解決線(xiàn)程之間的同步問(wèn)題,使得多線(xiàn)程設計更易發(fā)揮作用。用Java設計動(dòng)畫(huà)以及設計多媒體應用實(shí)例時(shí)會(huì )廣泛地使用到多線(xiàn)程,在后面幾章你將看到多線(xiàn)程的巨大作用,當然,現在必須先學(xué)習一些多線(xiàn)程的基本知識,慢慢地你就體會(huì )到它的優(yōu)越性。
5.1.2 線(xiàn)程的狀態(tài)
如同進(jìn)程有等待、運行、就緒等狀態(tài)一樣,線(xiàn)程也有其狀態(tài)。
當一個(gè)線(xiàn)程通過(guò)new被創(chuàng )建但還未運行時(shí),稱(chēng)此線(xiàn)程處于準備狀態(tài)(new狀態(tài))。當線(xiàn)程調用了start()方法或執行run()方法后,則線(xiàn)程處于可運行狀態(tài)。若在等待與其它線(xiàn)程共享資源,則稱(chēng)線(xiàn)程處于等待狀態(tài)。線(xiàn)程的另一個(gè)狀態(tài)稱(chēng)為不可運行(notrunnable)狀態(tài),此時(shí)線(xiàn)程不僅等分享處理器資源,而且在等待某個(gè)能使它返回可運行狀態(tài)的事件,例如被方法suspend()掛起的進(jìn)程就要等待方法resume()方可被喚醒。當調用了stop()方法或線(xiàn)程執行完畢,則線(xiàn)程進(jìn)入死亡(dead)狀態(tài)。線(xiàn)程的各個(gè)狀態(tài)之間的轉換關(guān)系見(jiàn)圖5.2。
5.1.3 創(chuàng )建線(xiàn)程
在了解基本概念后,下面學(xué)習如何在Java中創(chuàng )建多線(xiàn)程。
Java通過(guò)java.lang.Thread類(lèi)來(lái)支持多線(xiàn)程。在Thread類(lèi)中封裝了獨立的有關(guān)線(xiàn)程執行的數據和方法,并將多線(xiàn)程與面向對象的結構合為一體。
Java提供了兩種方法創(chuàng )建線(xiàn)程,一種是繼承Thread類(lèi),另一種則是實(shí)現接口Runnable。
1.繼承Thread類(lèi)
通過(guò)繼承Thread類(lèi)創(chuàng )建線(xiàn)程十分簡(jiǎn)單,只需要重載run()方法提供執行入口就可以,下面我們通過(guò)例5.1來(lái)解釋說(shuō)明。
例5.1 ThreadTest1.java。
運行結果:(略)
ThreadTest2創(chuàng )建thread1與thread2的方法(11~14行)與ThreadTest1不同,ThreadTest2直接創(chuàng )建了一個(gè)Thread的對象,并將Myclass的對象作為參數傳給Thread的構造方法。任何實(shí)現了Runnable接口的類(lèi)的對象都可以作Thread構造方法的參數。在main()方法中,其余部分程序ThreadTest2與ThreadTest1.java相同。
ThreadTest2的MyClass類(lèi)(31~59行)實(shí)現了接口Runnable,注意MyClass的構造方法實(shí)現了name這一類(lèi)變量,實(shí)現run()時(shí)不需要調用Thread的方法getName(),但在實(shí)現randomWait()時(shí)要使用Thread.currentThread().Sleep()(39行),因為Runnable接口并未提供方法sleep(),因而實(shí)現時(shí)必須調用Thread的類(lèi)方法currentThread()來(lái)調用sleep()。
事實(shí)上,無(wú)論用繼承Thread的方法或用實(shí)現接口Runnable的方法來(lái)實(shí)現多線(xiàn)程,在程序書(shū)寫(xiě)時(shí)區別不大,只需概念清楚略加注意便可。使用繼承Thread類(lèi)的方法比較簡(jiǎn)單易懂,實(shí)現方便。但如果你創(chuàng )建的Thread需要是某個(gè)其它類(lèi)的子類(lèi)時(shí),使用繼承Thread的方法就會(huì )出麻煩。比如,實(shí)現Applet時(shí),每個(gè)applet必須是java.applet.Applet的子類(lèi),此時(shí)想要實(shí)現多線(xiàn)程,只有通過(guò)使用Runnable接口,當然,使用Runnable接口來(lái)實(shí)現線(xiàn)程,在書(shū)寫(xiě)時(shí)會(huì )比較麻煩,因為你將不得不多做一些工作才可調用Thread的方法。
3.線(xiàn)程同步
在使用多線(xiàn)程時(shí),由于可以共享資源,有時(shí)就會(huì )發(fā)生沖突。舉一個(gè)簡(jiǎn)單的例子,有兩個(gè)線(xiàn)程thread1負責寫(xiě),thread2負責讀,當它們操作同一個(gè)對象時(shí),會(huì )發(fā)現由于thread1與thread2是同時(shí)執行的,因此可能thread1修改了數據而thread2讀出的仍為舊數據,此時(shí)用戶(hù)將無(wú)法獲得預期的結果。問(wèn)題之所以產(chǎn)生主要是由于資源使用協(xié)調不當(不同步)造成的。以前,這個(gè)問(wèn)題一般由操作系統解決,而Java提供了自己協(xié)調資源的方法。
Java提供了同步方法和同步狀態(tài)來(lái)協(xié)調資源。Java規定:被宣布為同步(使用Synchronized關(guān)鍵字)的方法,對象或類(lèi)數據,在任何一個(gè)時(shí)刻只能被一個(gè)線(xiàn)程使用。通過(guò)這種方式使資源合理使用,達到線(xiàn)程同步的目的。
我們將程序5.1的類(lèi)MyThread中的方法run()改成如下所示(見(jiàn)程序片段5.3),并加入一個(gè)類(lèi)SynchronizedShow實(shí)現同步,大家可以運行看到執行結果:每次執行Show()的只有一個(gè)線(xiàn)程。
例5.3 ThreadTest3.java片段
public void run(){
int i=0;
while(keepRunning) i++;//i代表循環(huán)次數
//輸出結果
SynchronizedShow.show(getName(),i);
SynchronizedShow.println(getName()+"isdead!");
}
class SychronizedShow{
//方法show(String,int)被宣布為同步的方法,因此每次只有一個(gè)線(xiàn)程能調用這個(gè)方法
public static synchronized voidshow(String,name,int i){
int k;
k=i;
for(intj=0;j<=3;j++){
MyThread t=(Mythread)Thread.currentThread();
t.randomWait();
System.out.println("Iam"+name+"—— I haverun"+k+" times.");
k++;
}
}
}
運行結果(略)
另外,利用Synchronized可以鎖定對象。
例如:Synchronized(某個(gè)對象A){
//程序塊
}
在此程序塊中,對于相同的對象A,在任何時(shí)候只可以有一個(gè)線(xiàn)程在此代碼中執行,但對于不同的對象還是有很多個(gè)線(xiàn)程同時(shí)執行的。用同樣的方法也可以協(xié)調類(lèi)數據,例如:
Synchroinzed(new欲鎖定的類(lèi)().getmethod()){
//程序塊
}
方法getmethod()是用來(lái)獲取類(lèi)數據的,這樣通過(guò)利用Synchronized這一關(guān)鍵字,我們可以自由協(xié)調對象實(shí)體的各種數據。
除了簡(jiǎn)單使用Synchronized這一關(guān)鍵字外,Java有一套復雜的同步機制,其基本原理采用了C.A.R.Hoare提出的,并已被廣泛使用的監視規則和條件變量規則。在Java中,所有的類(lèi)與對象都和管程(monitor)聯(lián)系在一起。當一個(gè)對象獲得管程(monitor)時(shí),它就可以執行同步方法,直至它讓出管程(monitor),其它對象方可進(jìn)入同步方法。當一個(gè)方法被完成時(shí),它將自動(dòng)讓管理(monitor),另外執行某些方法例如wait(),suspend()等時(shí)也會(huì )讓出管程(monitor)。
讀者可以試著(zhù)將例5.1的main()方法改成以下形式(風(fēng)險5.4)執行一下。人們會(huì )發(fā)現與前面利用Synchronized的結果類(lèi)似,原因是thread1執行suspend()后被掛起直至resume()方被喚醒。因此用這種方法也可以實(shí)現同步。
例5.4 Thread Test4.java的程序片斷。
public static void main(String args[])
throws java.io.IOException{
System.out.println("If want toshow the result,press return");
MyThread thread1=newMyThread("thread1");
MyThread thread2=newMyThread("thread2");
thread1.start();
thread2.start();
char ch;
while((ch=(char)System.in.read())!=‘\n‘);
thread1.tStart();
//將線(xiàn)程thread1掛起
thread1.suspend();
thread2.tStart();
while(thread2.isAlive());
//線(xiàn)程thread2進(jìn)入死亡狀態(tài)后,釋放線(xiàn)程thread1
thread1.resume();
while(thread1.isAlive());
/*{
you can do anything that youwant to do here.
}
*/
System.out.println("The testis end.");
}
運行結果:(略)
4.進(jìn)一步學(xué)習
上幾節我們學(xué)習了定義Thread的兩種方法,線(xiàn)程的同步的基本知識。在本節中我們將進(jìn)一步給出有關(guān)線(xiàn)程的一些更詳細的內容。
(1)若干常用的方法
首先,我們給出一些Thread類(lèi)中最常用的方法供大學(xué)參考。
■currentThread()
這是一個(gè)類(lèi)方法,返回當前正在執行的線(xiàn)程。
■isAlive()
判別一個(gè)線(xiàn)程是否仍然活著(zhù),包括這個(gè)線(xiàn)程正在執行或有機會(huì )被執行。返回一個(gè)布爾值。
■suspend()
懸掛起某個(gè)線(xiàn)程,并使得這個(gè)線(xiàn)程只可被resume()方法激活。
■resume()
與suspend()配合使用。喚醒線(xiàn)程。
■yeild()
強迫線(xiàn)程交出執行權利供其它線(xiàn)程使用。
■stop()
使線(xiàn)程進(jìn)入死亡(dead)狀態(tài)。
這些方法是線(xiàn)程中最常被使用到的方法,若要詳細了解更多的內容可以查閱Java的API。
(2)線(xiàn)程優(yōu)先級與調度
由于我我們一般使用的計算機是單CPU的,所以在執行多線(xiàn)程程序時(shí)需進(jìn)行線(xiàn)程調度。線(xiàn)程調度是由線(xiàn)程的優(yōu)先級決定的。高優(yōu)先級的線(xiàn)程總是先運行的。Java采用的是搶占式(preemptive)的調度方式,即當高優(yōu)先級的線(xiàn)程進(jìn)入可運行(runnable)狀態(tài)時(shí),會(huì )搶占低優(yōu)先級的線(xiàn)程的位置,并開(kāi)始執行。當同時(shí)有兩個(gè)或兩個(gè)以上的線(xiàn)程具有高優(yōu)先級并進(jìn)入可運行狀態(tài),Java的調度會(huì )自動(dòng)在這些線(xiàn)程間交替調度執行。
在Java中,Thread類(lèi)中預定義了三個(gè)常量:
MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY,
一個(gè)線(xiàn)程的優(yōu)先級應在MAX_PRIORITY與MIN_PRIORITY之間。NORM_PRIORITY是缺省的優(yōu)先級值,一般是MIN_PRIORITY與MAX_PRIORITY的平均值。
在Java中,Thread類(lèi)提供了方法設置和獲取優(yōu)先級。
setPriority(int) 用于設置線(xiàn)程的優(yōu)先數
setPriority() 用于獲取線(xiàn)程的優(yōu)先數
(3)線(xiàn)程組(Thread Group)
線(xiàn)程組是包括了許多線(xiàn)程的對象集。每個(gè)線(xiàn)程有自己特定的線(xiàn)程組。一個(gè)線(xiàn)程在創(chuàng )建時(shí)就屬于某個(gè)線(xiàn)程組,直至其執行結束,此線(xiàn)程不可更改其所屬的線(xiàn)程組。Thread類(lèi)中提供了構造方法使創(chuàng )建線(xiàn)程時(shí)同時(shí)決定其線(xiàn)程組。Thread類(lèi)總共提供了六種構造方法:
Thread();
Thread(String);
Thread(Runnable);
Thread(Runnable,String);
Thread(ThreadGroup,String);
Thread(ThreadGroup,Runnable,String);
前四種缺省了線(xiàn)程組,表示所創(chuàng )建的線(xiàn)程屬于main線(xiàn)程組,后兩種則指定了所創(chuàng )建的線(xiàn)程的線(xiàn)程組。線(xiàn)程可以訪(fǎng)問(wèn)自己所在的線(xiàn)程組,但不能訪(fǎng)問(wèn)本線(xiàn)程組的父類(lèi)。對線(xiàn)程組進(jìn)行操作就是對線(xiàn)程組中的各個(gè)線(xiàn)程同時(shí)進(jìn)行操作。
線(xiàn)程組的構造方法:
ThreadGroup(String groupName)
創(chuàng )建名為groupName的線(xiàn)程組,該線(xiàn)程組的父類(lèi)為當前線(xiàn)程所在程組。
ThreadGroup(ThreadGroup parent,StringgroupName)
創(chuàng )建名為groupName的線(xiàn)程組,該線(xiàn)程組的父類(lèi)是parent。
(4)wait()和notify()方法
Java在類(lèi)java.lang.Object中定義了wait()和notify()方法,調用它們也可以實(shí)現線(xiàn)程之間的同步。
■public final void wait(longmillseconds) throws InterruptedException
調用此方法時(shí),被調對象進(jìn)入等待狀態(tài),直到被喚醒或等待時(shí)間到。
■public final void notify()
喚醒一個(gè)對象內處于等待狀態(tài)的對象。
■public find void Allotify()
喚醒一個(gè)對象內所有處于等待狀態(tài)的對象。
5.2 Debugger的使用
至此我們已經(jīng)書(shū)寫(xiě)了不少程序,大家可能發(fā)覺(jué)出錯要調試很困難,其實(shí)java工具包中的Javadebugger為用戶(hù)提供了方便的調試機制。雖然jdb的界面不是很漂亮,但很有用,尤其對于調試多線(xiàn)程程序。
Java的debugger需要jdb命令激活。如下執行:
C:\>jdb
在執行之前,請用帶-g參數的javac對程序進(jìn)行編譯,本節我們使用了前一章中check.java,第一步先重新編譯方法如下:
C:\synetjava\java\exmples>javac -gcheck.java
用下面的方法可以進(jìn)入jdb,可以直接在jdb后緊接要調試的類(lèi)名,也可缺省,在進(jìn)入jdb后利用load載入要調試的類(lèi)。
C:\MyDemo\dawn>jdb Check
Initializing jdb...
0xe8d370:class(Check)
在Check這一類(lèi)名前的16進(jìn)制數是Check類(lèi)在Java運行時(shí)的標識。
進(jìn)入了jdb后,可以使用help來(lái)獲取所需的使用信息:
> help
** command list **
run [class [args]] --start execution of application‘s main class
threads [threadgroup] --list threads
thread <thread id> --set default thread
suspend [thread id(s)] --suspend threads (default: all)
resume [thread id(s)] --resume threads (default: all)
where [thread id] | all -- dump athread‘s stack
wherei [thread id] | all -- dump athread‘s stack, with pc info
up [n frames] --move up a thread‘s stack
down [n frames] --move down a thread‘s stack
kill <thread> <expr> --kill a thread with the given exceptionobject
interrupt <thread> --interrupt a thread
print <expr> --print value of expression
dump <expr> --print all object information
eval <expr> --evaluate expression (same as print)
set <lvalue> = <expr> --assign new value to field/variable/arrayelement
locals --print all local variables in current stackframe
classes --list currently known classes
class <class id> --show details of named class
methods <class id> --list a class‘s methods
fields <class id> --list a class‘s fields
threadgroups --list threadgroups
threadgroup <name> --set current threadgroup
stop in <classid>.<method>[(argument_type,...)]
--set a breakpoint in a method
stop at <class id>:<line>-- set a breakpoint at a line
clear <classid>.<method>[(argument_type,...)]
--clear a breakpoint in a method
clear <class id>:<line> --clear a breakpoint at a line
clear --list breakpoints
catch <class id> --break when specified exception thrown
ignore <class id> --cancel ‘catch‘ for the specified exception
watch [access|all] <classid>.<field name>
--watch access/modifications to a field
unwatch [access|all] <classid>.<field name>
--discontinue watching access/modifications toa field
trace methods [thread] -- trace methodentry and exit
untrace methods [thread] -- stoptracing method entry and exit
step --execute current line
step up --execute until the current method returns toits cal
ler
stepi --execute current instruction
next --step one line (step OVER calls)
cont --continue execution from breakpoint
list [line number|method] -- printsource code
use (or sourcepath) [source file path]
--display or change the source path
exclude [class id ... |"none"]
--do not report step or method events forspecified classes
classpath -- print classpath info fromtarget VM
monitor <command> --execute command each time the program stops
monitor -- listmonitors
unmonitor <monitor#> --delete a monitor
read <filename> --read and execute a command file
lock <expr> --print lock info for an object
threadlocks [thread id] -- print lockinfo for a thread
disablegc <expr> --prevent garbage collection of an object
enablegc <expr> --permit garbage collection of an object
!! --repeat last command
<n> <command> --repeat command n times
help (or ?) --list commands
version --print version information
exit (or quit) --exit debugger
<class id>: full class namewith package qualifiers or a
pattern with a leading or trailingwildcard (‘*‘).
<thread id>: thread number asreported in the ‘threads‘ command
<expr>: a Java(tm) ProgrammingLanguage expression.
Most common syntax is supported.
Startup commands can be placed ineither "jdb.ini" or".jdbrc"
in user.home or user.dir
>
利用命令stop in <classid>.<method>可以在方法中設置斷點(diǎn),用命令run運行方法。
>stop in Check.main
Breakpoint set Check.main
>run Check
running...
main[1]
Breakpoint hit: Check.main(Chech:18)
main[1] list
14 }
15 }
16 class Check{
17 public static voidmain(String args[]]){
18 => Son s=new Son();
19 s.speak();
20 }
21 }
main[1]
=>所指的地方即為斷點(diǎn)處,然后使用step命令進(jìn)行步調執行,結果提示斷點(diǎn)設置到了類(lèi)son中用list顯示,就可以清楚地看到斷點(diǎn)所在位置。
main[1]step
main[1]
Breakpoint hit:Son.<init>(Son:9)
main[1]list
5 void speak(String s){
6 System.out.println("Ilike "+s+".");
7 }
8 }
9 =>class Son extendsFather{
10 void speak(){
11 System.out.println("Myfather sys:");
12 super.speak();
13 super.speak("hunting");
用cont命令可以繼續執行下去,本例十分簡(jiǎn)單,所以立即給出了運行結果。我們還可以試一試help中顯示的其它命令,methodscheck顯示了check類(lèi)中定義的方法,memory顯示了java運行時(shí)刻提供的內存等等,用戶(hù)可以自己去試用一下。
main[1]cont
My father syas:main[1]
I am Father.
I like hunting.
//顯示check類(lèi)中定義的方法
main[1]methods Check
void main(String[])
void <init>()
//顯示java運行時(shí)刻提供的內存main[1]memory
Free:295928,total:1777656
//列出線(xiàn)程組
main[1]threadgrouts
1.(java.lang.ThreadGroup)0xe600b8system
2.(java.lang.ThreadGroup)0xe655e0 main
3.(java.lang.ThreadGroup)0xe8ddd8Check.main
//列出指定線(xiàn)程組中的線(xiàn)程
main[1]threads system
Group system:
1.(java.lang.Thread)0xe600d0 Frinalizertherad suspended
2.(java.lang.Thread)0xe65570 Debuggeragent running
3.(sun.tools.debug.BreakpointHandler)0xe8b080Breakpoint handler cond. waitin
Group main:
4.(java.lang.Thread)0xe600a8 mainsuspended
Group Check.main:
事實(shí)上,Jdb工具現在還存在不少缺陷正待改進(jìn),使用時(shí)有時(shí)會(huì )出現一些莫名其妙的錯誤,所以使用時(shí)請大家最好聯(lián)網(wǎng),便于查詢(xún)。
聯(lián)系客服