《Java語(yǔ)言程序設(shè)計(jì)》課件第8章_第1頁(yè)
《Java語(yǔ)言程序設(shè)計(jì)》課件第8章_第2頁(yè)
《Java語(yǔ)言程序設(shè)計(jì)》課件第8章_第3頁(yè)
《Java語(yǔ)言程序設(shè)計(jì)》課件第8章_第4頁(yè)
《Java語(yǔ)言程序設(shè)計(jì)》課件第8章_第5頁(yè)
已閱讀5頁(yè),還剩97頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

8.1線程初步

8.2創(chuàng)建線程

8.3線程操作

8.4線程同步

8.5小結(jié)

8.6習(xí)題

8.1.1認(rèn)識(shí)線程

1.解析一個(gè)運(yùn)行結(jié)果

例如,執(zhí)行一個(gè)程序,這個(gè)程序可以一邊顯示cat、dog和hare,一邊計(jì)算并顯示1~5的階乘。

如果在控制臺(tái)完成這樣的功能,按照要求,運(yùn)行結(jié)果如圖8-1所示。8.1線程初步圖8-1兩個(gè)線程同時(shí)工作如何在一個(gè)程序中定義并實(shí)現(xiàn)兩個(gè)功能的代碼段呢?也許這個(gè)問(wèn)題不難解決。可是如何讓它們并發(fā)執(zhí)行,這就是問(wèn)題的關(guān)鍵所在了。完成這個(gè)任務(wù)的主要程序如下:

publicstaticvoidmain(String[]args){

RecognizeThreadth1=newRecognizeThread(); //創(chuàng)建線程1

RecognizeThreadth2=newRecognizeThread(); //創(chuàng)建線程2

th1.start(); //啟動(dòng)線程1

th2.start(); //啟動(dòng)線程2

}publicvoidrun(){//執(zhí)行線程

while(true){

if(th1運(yùn)行){

//顯示動(dòng)物名稱

}

else{//(th2運(yùn)行)

//計(jì)算并顯示階乘

}

}

}由于要把程序分成兩個(gè)彼此分離、能獨(dú)立運(yùn)行的子任務(wù),就要考慮線程的創(chuàng)建。Main()方法中創(chuàng)建了兩個(gè)線程th1和th2;RecognizeThread是一個(gè)自定義類,它繼承了Thread類。CPU通過(guò)某些底層機(jī)制進(jìn)行時(shí)間分配,將時(shí)間片段分配給每個(gè)線程。因此就要事先啟動(dòng)線程,等待CPU進(jìn)行時(shí)間分配,這就是start()方法的任務(wù)。當(dāng)某個(gè)線程得到CPU分配的時(shí)間片時(shí),開始執(zhí)行自己的代碼序列,完成相應(yīng)的功能。這些功能代碼被放置在run()方法中,根據(jù)當(dāng)前線程的有效性判斷,然后開始執(zhí)行相關(guān)代碼。

2.線程的基本操作

每個(gè)Java程序至少有一個(gè)線程,被稱為主線程,可以通過(guò)Thread類的currentThread方法看到它。Thread類(線程類)是java.lang包中的一個(gè)專門用來(lái)創(chuàng)建線程和對(duì)線程進(jìn)行操作的類。下面的幾個(gè)例子將運(yùn)用Thread類提供的方法,對(duì)線程進(jìn)行一些基本操作,從而對(duì)線程有一個(gè)更加具體的認(rèn)識(shí)?!纠?-1】得到主線程。

程序清單如下:

publicclassMainThread{

publicstaticvoidmain(String[]args){

//創(chuàng)建一個(gè)線程對(duì)象,并使它得到當(dāng)前正在執(zhí)行的線程對(duì)象的引用

Threadthread=Thread.currentThread();

System.out.println();

//getName():Thread類的方法,得到這個(gè)線程的名字

System.out.println(“mainthreadisnamed:”+thread.getName());

}

}

程序運(yùn)行結(jié)果如圖8-2所示。圖8-2MainThread.java的運(yùn)行結(jié)果可見(jiàn),在Java程序啟動(dòng)時(shí),它有一個(gè)主線程,名字被默認(rèn)為main。如果需要再創(chuàng)建另外的線程,詳細(xì)內(nèi)容將在8.2節(jié)中講解。

【例8-2】命名線程。

程序清單如下:

publicclassSetName{

publicstaticvoidmain(String[]args){

//得到當(dāng)前線程

Threadthread=Thread.currentThread();

System.out.println();System.out.println("mainthreadisnamed:"+thread.getName());

//將這個(gè)線程的名字改為參數(shù)的名字

thread.setName("Java");

System.out.println();

System.out.println("mainthreadisnownamed:"+thread.getName());

}

}

程序運(yùn)行結(jié)果如圖8-3所示。圖8-3SetName.java的運(yùn)行結(jié)果命名線程是區(qū)分線程的方法之一。

有時(shí)會(huì)希望暫停某個(gè)線程的執(zhí)行,這時(shí)可以用sleep方法來(lái)實(shí)現(xiàn):將指定時(shí)間量傳遞給sleep方法,線程將暫停,直至此時(shí)間結(jié)束再繼續(xù)執(zhí)行。sleep方法的使用有以下兩種形式:

staticvoidsleep(longn):暫停n毫秒。

staticvoidsleep(longm,intn):暫停1000000×m+n納秒。

【例8-3】暫停線程。

publicclassSleepThread{

publicstaticvoidmain(String[]args){

try{

System.out.println("Let\'s");//主線程暫停1秒鐘,再繼續(xù)往下執(zhí)行

Thread.sleep(1000);

System.out.println("study");

Thread.sleep(1000);//同上

System.out.println("conception");

Thread.sleep(1000);//同上

System.out.println("of");

Thread.sleep(1000);//同上

System.out.println("thread");

}

catch(InterruptedExceptione){}

}

}

程序運(yùn)行結(jié)果如圖8-4所示。圖8-4SleepThread.java的運(yùn)行結(jié)果關(guān)于這個(gè)程序的說(shuō)明:

(1)由于sleep方法是靜態(tài)方法,因此在程序中可以采用“Thread.sleep(1000)”的形式,表明將主線程暫停1000毫秒。

(2)程序在執(zhí)行時(shí),結(jié)果的呈現(xiàn)是有暫停的。

(3)在調(diào)用sleep方法時(shí),必須要捕獲異常。8.1.2線程的生命周期

Java語(yǔ)言使用Thread類及其子類的對(duì)象來(lái)表示線程。新建的線程在它的一個(gè)完整的生命周期中通常要經(jīng)歷新生、就緒、運(yùn)行、阻塞和死亡等五種狀態(tài)。

1)新生狀態(tài)

當(dāng)用new關(guān)鍵字和某線程類的構(gòu)造方法創(chuàng)建一個(gè)線程對(duì)象后,這個(gè)線程對(duì)象就處于新生狀態(tài),此時(shí)它已經(jīng)有了相應(yīng)的內(nèi)存空間,并被初始化。處于該狀態(tài)的線程可通過(guò)調(diào)用start()方法進(jìn)入就緒狀態(tài)。

2)就緒狀態(tài)

處于就緒狀態(tài)的線程已經(jīng)具備了運(yùn)行的條件,但尚未分配到CPU資源,因而它將進(jìn)入線程隊(duì)列排隊(duì),等待系統(tǒng)為它分配CPU。一旦獲得CPU資源,則該線程進(jìn)入運(yùn)行狀態(tài),并自動(dòng)調(diào)用自己的run()方法。此時(shí),它脫離創(chuàng)建它的主線程,獨(dú)立開始了自己的生命周期。

3)運(yùn)行狀態(tài)

進(jìn)入運(yùn)行狀態(tài)的線程即可執(zhí)行自己的run()方法中的代碼。若遇到下列情況之一,則將終止run()方法的執(zhí)行:

(1)終止操作。調(diào)用當(dāng)前的stop()方法或destroy()方法進(jìn)入死亡狀態(tài)。

(2)等待操作。調(diào)用當(dāng)前線程的join(millis)方法或wait(millis)方法進(jìn)入阻塞狀態(tài)。當(dāng)線程進(jìn)入阻塞狀態(tài)時(shí),在millis毫秒內(nèi)可由其他線程調(diào)用notify()或notifyAll()方法將其喚醒,進(jìn)入就緒狀態(tài);在millis毫秒內(nèi)若不喚醒,則須等待到當(dāng)前線程結(jié)束。

(3)睡眠操作。調(diào)用sleep(millis)方法來(lái)實(shí)現(xiàn)。當(dāng)前線程停止執(zhí)行后即處于阻塞狀態(tài),待睡眠millis毫秒之后重新進(jìn)入就緒狀態(tài)。

(4)掛起操作。通過(guò)調(diào)用suspend()方法來(lái)實(shí)現(xiàn)。當(dāng)前線程一旦掛起,即進(jìn)入阻塞狀態(tài),只有當(dāng)其他線程調(diào)用當(dāng)前線程的resume()方法后,才能使其進(jìn)入就緒狀態(tài)。

(5)退讓操作。通過(guò)調(diào)用yield()方法,“暗示”線程調(diào)度機(jī):當(dāng)前線程的工作已經(jīng)快完成,可以讓別的線程使用CPU了;而當(dāng)前線程則停止執(zhí)行,進(jìn)入就緒狀態(tài)。

(6)當(dāng)前線程要求輸入/輸出時(shí),進(jìn)入阻塞狀態(tài)。

(7)若分配給當(dāng)前線程的時(shí)間片已用完,則當(dāng)前線程進(jìn)入就緒狀態(tài)。若當(dāng)前線程的run()方法執(zhí)行完畢,則線程進(jìn)入死亡狀態(tài)。

4)阻塞狀態(tài)

一個(gè)正在執(zhí)行的線程在某些特殊情況下,如執(zhí)行了join()或sleep()方法,或等待I/O設(shè)備的使用權(quán),那么它將讓出CPU并暫時(shí)終止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)。阻塞時(shí)該線程不能進(jìn)入就緒隊(duì)列,只有當(dāng)引起阻塞的原因被消除時(shí),才能轉(zhuǎn)入就緒狀態(tài),重新進(jìn)入線程隊(duì)列中排隊(duì)等待CPU資源,以便從原來(lái)終止處繼續(xù)運(yùn)行。

5)死亡狀態(tài)

這是線程最后的狀態(tài),處于死亡狀態(tài)的線程將永遠(yuǎn)不再執(zhí)行。線程死亡有兩個(gè)原因:一是正常運(yùn)行的線程完成了它的全部工作;二是線程被提前強(qiáng)制性地終止,例如,通過(guò)執(zhí)行stop()或destroy()方法來(lái)終止線程。不過(guò)Thread類的stop()方法已經(jīng)被廢棄,因?yàn)樗话踩共僮飨到y(tǒng)訪問(wèn)監(jiān)控器處于不穩(wěn)定的狀態(tài)。每個(gè)Java程序都有一個(gè)默認(rèn)的主線程。對(duì)于JavaApplication程序,在執(zhí)行main()方法時(shí)會(huì)啟動(dòng)一個(gè)線程,這就是“主線程”,它負(fù)責(zé)執(zhí)行main()方法;對(duì)于JavaApplet程序,當(dāng)瀏覽器內(nèi)置的JVM創(chuàng)建其主類的一個(gè)對(duì)象后,會(huì)啟動(dòng)一個(gè)專門的線程——稱為JavaApplet的主線程,它讓主類的對(duì)象調(diào)用start()方法,開始執(zhí)行JavaApplet。要想實(shí)現(xiàn)多線程,必須在主線程中創(chuàng)建其他新的線程對(duì)象。8.2創(chuàng)建線程8.2.1繼承Thread類來(lái)創(chuàng)建線程

Thread類(線程類)是java.lang包中的一個(gè)專門用來(lái)創(chuàng)建線程并對(duì)線程進(jìn)行操作的類。這個(gè)類已經(jīng)具有創(chuàng)建和運(yùn)行線程所必要的架構(gòu)。Java在Thread類中定義了許多方法,有助于更好地運(yùn)用和處理線程。這些方法可分為以下四組:

(1)構(gòu)造方法:用于創(chuàng)建用戶的線程對(duì)象。

(2)?run()方法:用于定義用戶線程所要執(zhí)行的操作。

(3)改變線程狀態(tài)的方法:如start()、sleep()、stop()、suspend()、resume()、yield()和wait()等方法,是最常用的一組方法。

(4)其他方法:如setPriority()、setName()等。

【例8-4】有一個(gè)數(shù)從0開始不斷遞增,顯示遞增過(guò)程。

將這個(gè)過(guò)程設(shè)計(jì)成一個(gè)線程,并在控制臺(tái)運(yùn)行。實(shí)現(xiàn)這個(gè)設(shè)計(jì)過(guò)程的步驟如下:

(1)繼承Thread類,通過(guò)其構(gòu)造線程對(duì)象,這時(shí)線程處于新生狀態(tài)。

(2)利用start()方法啟動(dòng)該線程,使其進(jìn)入線程隊(duì)列排隊(duì),等待系統(tǒng)為它分配CPU,即處于就緒狀態(tài)。

(3)覆蓋Thread類的run()方法,從而完成數(shù)據(jù)的遞增和顯示。當(dāng)線程得到CPU資源,處于運(yùn)行狀態(tài)時(shí),會(huì)自動(dòng)調(diào)用這個(gè)方法來(lái)執(zhí)行。程序清單如下:

publicclassSimpleThreadextendsThread{//繼承Thread類

intcount=0;

publicstaticvoidmain(String[]args){

SimpleThreadaddThread=newSimpleThread(); //創(chuàng)建一個(gè)線程

addThread.start(); //啟動(dòng)線程

}publicvoidrun(){

//覆蓋run()方法:遞增并顯示

while(true){

try{

sleep(500);

//主線程休眠500毫秒

}

catch(InterruptedExceptione){}

System.out.println(count++);

}

}

}

程序運(yùn)行結(jié)果如圖8-5所示。圖8-5SimpleThread.java的運(yùn)行結(jié)果這個(gè)結(jié)果將不停地顯示下去。事實(shí)上run()方法總會(huì)有某種形式的循環(huán),使得線程一直運(yùn)行下去直到不再需要,所以要設(shè)定跳出循環(huán)的條件(在后面的例子中會(huì)看到)。

【例8-5】重新看本章的第一個(gè)程序。這個(gè)程序可以一邊顯示cat、dog和hare,一邊計(jì)算并顯示1~5的階乘。程序清單如下:

publicclassRecognizeThreadextendsThread{

intcount=0,num=4,i=1,temp=1;

String[]array={“cat”,“dog”,“hare”};

publicstaticvoidmain(String[]args){

RecognizeThreadth1=newRecognizeThread();

//創(chuàng)建線程th1

RecognizeThreadth2=newRecognizeThread();

//創(chuàng)建線程th2//給線程起名——在執(zhí)行run()方法時(shí),可根據(jù)當(dāng)前執(zhí)行線程的名稱來(lái)判斷執(zhí)行

//哪個(gè)代碼段

th1.setName("thread1");

th2.setName("thread2");

th1.start();//啟動(dòng)線程th1

th2.start();//啟動(dòng)線程th2

}

publicvoidrun(){//執(zhí)行線程

while(true){//線程休眠500毫秒,控制顯示速度

try{

sleep(500);

}

catch(InterruptedExceptione){}

//如果當(dāng)前執(zhí)行的是線程th1,則顯示動(dòng)物名稱,否則計(jì)算并顯示階乘

if((Thread.currentThread().getName()).equals("thread1")){

System.out.println(array[count++]);

if(count>2)count=0;

}else{

temp=temp*i;

System.out.println(i+"!="+temp);

i++;

if(i>4){

i=1;

temp=1;

}

}

}

}

}例8-5使用了繼承Thread類的方法。當(dāng)一個(gè)類必須繼承另一個(gè)類,而且又要完成線程的控制時(shí),就不能繼承Thread類,因?yàn)镴ava不支持多重繼承。這時(shí)可以這樣考慮:定義一個(gè)A類,使得A繼承Thread類;在主類中完成線程的創(chuàng)建和啟動(dòng);在A中完成run()方法的覆蓋。例8-6就是按照這個(gè)思路設(shè)計(jì)的。

【例8-6】設(shè)計(jì)一個(gè)圖形界面,用一個(gè)按鈕啟動(dòng)計(jì)數(shù)器做遞增運(yùn)算,并在文本框里顯示數(shù)據(jù)(從0開始計(jì)數(shù))。importjava.awt.*;

importjavax.swing.*;

importjava.awt.event.*;

publicclassComplicateThreadextendsJAppletimplementsActionListener{

ThreadClassaddThread; //聲明線程對(duì)象

JTextFieldtf=newJTextField(10); //設(shè)計(jì)顯示文本框

JButtonbt=newJButton("Increase"); //設(shè)計(jì)按鈕publicvoidinit(){

Containercp=getContentPane();

cp.setLayout(newFlowLayout());//完成容器布局的設(shè)計(jì)

cp.add(tf);

//加載組件

cp.add(bt);

bt.addActionListener(this);

//完成事件監(jiān)聽

}publicvoidactionPerformed(ActionEvente){

if(addThread==null){

//判斷是否第一次操作按鈕

addThread=newThreadClass(); //創(chuàng)建線程

addThread.start(); //啟動(dòng)線程

}

}

//定義內(nèi)部類ThreadClass,使其成為線程類;內(nèi)部類使tf可用classThreadClassextendsThread{

intcount=0; //計(jì)數(shù)器初始化

publicvoidrun(){

while(true){

try{

sleep(500);

}

catch(InterruptedExceptione){}

tf.setText(Integer.toString(count++));

//計(jì)數(shù)并顯示

}

}

}

}

程序運(yùn)行結(jié)果如圖8-6所示。圖8-6ComplicateThread.java的運(yùn)行結(jié)果在這個(gè)例題中,ComplicateThread類必須繼承JApplet類才能設(shè)計(jì)成圖形界面,把線程類的定義交給了其他類去完成。如果ComplicateThread類本身又想成為一個(gè)線程類,完成線程的整個(gè)控制時(shí),如何設(shè)計(jì)呢?這就需要用到創(chuàng)建線程的另外一種方法——實(shí)現(xiàn)Runnable接口。8.2.2實(shí)現(xiàn)Runnable接口來(lái)創(chuàng)建線程

創(chuàng)建線程的另一個(gè)途徑是實(shí)現(xiàn)Runnable接口。Runnable接口提供了一個(gè)名為run()的方法,用戶新建線程的操作代碼應(yīng)該放在這個(gè)方法中去定義。當(dāng)用戶程序需要建立新線程時(shí),只需將實(shí)現(xiàn)了Runnable接口類的對(duì)象作為參數(shù)傳遞給Thread類的構(gòu)造方法即可。與Runnable相關(guān)的Thread類的構(gòu)造方法如下:

Thread(Runnabletarget):使用Runnable對(duì)象構(gòu)造一個(gè)新的Thread對(duì)象。

Thread(Runnabletarget,Stringname):使用Runnable對(duì)象和一個(gè)名字構(gòu)造一個(gè)新的Thread對(duì)象?!纠?-7】顯示當(dāng)前時(shí)間,實(shí)現(xiàn)Runnable接口。

importjavax.swing.*;

importjava.awt.*;

importjava.util.*;//定義了date類

publicclassCreatRunnableextendsJAppletimplementsRunnable{

Threadth;

Stringstr="";//覆蓋Runnable接口中的run()方法

publicvoidrun(){

while(true){

try{

th.sleep(1000);

}

catch(InterruptedExceptione){}

repaint();

}

}//初始化方法,完成線程的創(chuàng)建

publicvoidinit(){

if(th==null){

th=newThread(this,"clock");

th.start();

}

}

//繪制日期

publicvoidpaint(Graphicsg){g.setFont(newFont(“TimesRoman”,Font.PLAIN,18));

g.setColor(Color.WHITE);

//用白色把上一次顯示的字符串重寫一遍,達(dá)到清理痕跡的效果

g.drawString(str,10,10);

//str存放的是剛剛顯示過(guò)的字符串

g.setColor(Color.BLUE);//用藍(lán)色顯示新字符串的值

str=(newDate()).toString();

//得到當(dāng)前時(shí)間,并轉(zhuǎn)化成日期類的文字

g.drawString(str,10,10);//顯示當(dāng)前時(shí)間

}

}

程序運(yùn)行結(jié)果如圖8-7所示。圖8-7CreatRunnable.java的運(yùn)行結(jié)果8.3線程操作8.3.1線程等待

有兩個(gè)線程A和B,如果線程A對(duì)線程B執(zhí)行了B.join(),則線程A將被掛起,直到目標(biāo)線程B結(jié)束后才繼續(xù)執(zhí)行。這是線程間的等待關(guān)系,也算是一種協(xié)作。在這個(gè)過(guò)程中,可以利用線程的isAlive()方法檢查線程是否是活躍的。如果線程正在執(zhí)行,則該方法返回false,否則返回true?!纠?-8】線程等待示例。

classCustomThreadextendsThread{//定義了一個(gè)線程類

publicCustomThread(Stringname){

super(name);

start();

}

publicvoidrun(){//完成線程在運(yùn)行時(shí)的信息顯示

try{

for(inti=1;i<3;i++){System.out.println((Thread.currentThread()).getName()+"thread:"+i+"次.");

sleep(500);

}

}

catch(InterruptedExceptione){}

System.out.println((Thread.currentThread()).getName()+"threadending!");

}

}publicclassMethod_Join{//公共類的定義

publicstaticvoidmain(String[]args){

CustomThreadth1=newCustomThread("animal");

CustomThreadth2=newCustomThread("plant");

System.out.println("th1:"+th1.isAlive());

/*

try{

th1.join();//等待th1完成

th2.join();//等待th2完成

}catch(InterruptedExceptione){}

System.out.println("th1:"+th1.isAlive());

*/

System.out.println("Mainmethodend!All2threadsareended.");

}

}

程序運(yùn)行結(jié)果如圖8-8所示。圖8-8Method_Join.java的運(yùn)行結(jié)果主線程和線程th1、th2在分配CPU的時(shí)間內(nèi)輪流執(zhí)行。如果將程序中的注釋去掉,對(duì)th1和th2分別執(zhí)行join()方法,則主線程會(huì)停止運(yùn)行,直到th1和ht2執(zhí)行完后再繼續(xù)運(yùn)行。運(yùn)行結(jié)果如圖8-9所示。圖8-9線程等待的運(yùn)行結(jié)果8.3.2停止線程

通過(guò)Thread的start()方法啟動(dòng)線程后,將調(diào)用線程類的run()方法運(yùn)行線程,當(dāng)run()方法運(yùn)行結(jié)束時(shí),線程也就結(jié)束了。有時(shí)線程的run()方法會(huì)被寫成無(wú)限循環(huán)的形式,這就意味著,除非有某個(gè)條件使得run()終止,否則它將永遠(yuǎn)運(yùn)行下去。因?yàn)門hread類的stop()方法已經(jīng)被廢棄,不能通過(guò)它來(lái)終止線程,所以要設(shè)定跳出循環(huán)的條件。

例8-9所示程序StopThread.java介紹了停止線程的方法,即停止一個(gè)每隔200毫秒打印一條消息的線程。【例8-9】StopThread.java程序示例。

classCustomThreadextendsThread{//定義了一個(gè)線程類

intcounter=1;

booleanflag=true;//設(shè)置標(biāo)志

publicvoidrun(){

System.out.println("Threadbegin:");

while(flag){

try{System.out.println("Threadrun:"+counter++);

Thread.sleep(200);//每隔200毫秒顯示一次信息

}

catch(InterruptedExceptione){}

}

}

publicvoidstopThread(){//改變標(biāo)志,使線程停止運(yùn)行

flag=false;

System.out.println("Threadisvoer.");

}

}publicclassStopThread{//公共類的定義

publicstaticvoidmain(String[]args){

CustomThreadth=newCustomThread();

th.start();

try{

Thread.sleep(1000);

//主線程暫停1秒鐘,讓線程th運(yùn)行

}

catch(InterruptedExceptione){}

th.stopThread();//終止th的運(yùn)行

}

}

程序運(yùn)行結(jié)果如圖8-10所示。圖8-10StopThread.java的運(yùn)行結(jié)果在這個(gè)程序里,改變循環(huán)條件是通過(guò)一個(gè)自定義方法來(lái)實(shí)現(xiàn)的——stopThread()。主線程創(chuàng)建并啟動(dòng)了線程th,這時(shí)主線程暫停1秒鐘。在這1秒鐘里,線程th每隔200毫秒打印一條信息。1秒鐘過(guò)后,主線程執(zhí)行了th.stopThread();語(yǔ)句,改變了循環(huán)標(biāo)志,使得線程th終止。

在實(shí)際編程中,循環(huán)條件要根據(jù)不同的程序意圖進(jìn)行設(shè)置。設(shè)定循環(huán)標(biāo)志僅僅是其中的一種方法。8.3.3線程調(diào)度

在Java系統(tǒng)中,運(yùn)行的每個(gè)線程都有優(yōu)先級(jí)。線程的優(yōu)先級(jí)能告訴調(diào)度程序該線程的重要性如何。盡管CPU處理現(xiàn)有線程集的順序是不確定的,但是如果有許多線程被阻塞并在等待運(yùn)行,那么調(diào)度程序?qū)A向于讓優(yōu)先級(jí)最高的線程先運(yùn)行。然而,這并不意味著優(yōu)先級(jí)較低的線程將得不到執(zhí)行,它僅僅是執(zhí)行的頻率較低,即優(yōu)先級(jí)不會(huì)導(dǎo)致死鎖。

JDK有10個(gè)優(yōu)先級(jí)別(用1~10表示),數(shù)值越大,優(yōu)先級(jí)越高。Java用標(biāo)識(shí)符常量MIN_PRIORITY表示優(yōu)先級(jí)為1,NORM_PRIORITY表示優(yōu)先級(jí)為5,MAX_PRIORITY表示優(yōu)先級(jí)為10,未設(shè)定優(yōu)先級(jí)的線程其優(yōu)先級(jí)取缺省值5。其他級(jí)別的優(yōu)先級(jí)既可以直接用1~10之間的正整數(shù)來(lái)設(shè)置,也可以在標(biāo)識(shí)符常量的基礎(chǔ)上加一個(gè)常數(shù)。例如,下面的語(yǔ)句將線程優(yōu)先級(jí)設(shè)置為8。

SetPriority(Thread.NORM_PRIORITY+3);

例8-10中,程序Priority.java演示了優(yōu)先級(jí)的設(shè)置。在這個(gè)程序里,有兩個(gè)線程用來(lái)從0開始計(jì)數(shù),經(jīng)過(guò)5秒鐘后,顯示兩個(gè)線程的最終計(jì)數(shù)值。如果把兩個(gè)線程的優(yōu)先級(jí)都定義成NORM_PRIORITY,運(yùn)行結(jié)果會(huì)大體相當(dāng)。

【例8-10】Priority.java程序示例。classCustomThreadimplementsRunnable

{ //定義了一個(gè)線程類

Threadthread;

intcounter=0;

booleanflag=true;

publicCustomThread(intp){

thread=newThread(this); //創(chuàng)建線程

thread.setPriority(p); //設(shè)置線程的優(yōu)先級(jí)

thread.start(); //啟動(dòng)線程

}publicvoidrun(){

//完成線程在運(yùn)行時(shí)的信息顯示

while(flag){

counter++;

}

}

//定義stop()方法:改變標(biāo)志,使線程停止運(yùn)行

publicvoidstop(){

flag=false;

}

}publicclassPriority{//公共類的定義

publicstaticvoidmain(String[]args){

CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY);

CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY);

try{

Thread.sleep(5000);

//主線程暫停5秒鐘,即讓th1和th2計(jì)數(shù)5秒

}

catch(InterruptedExceptione){}

th1.stop();//終止th1的運(yùn)行

th2.stop();//終止th2的運(yùn)行

System.out.println("th1counted::"+th1.counter);

System.out.println("th2counted::"+th2.counter);

}

}

程序運(yùn)行結(jié)果如圖8-11所示。圖8-11Priority.java的運(yùn)行結(jié)果程序中有兩條語(yǔ)句對(duì)線程設(shè)置優(yōu)先級(jí):

CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY);

CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY);

現(xiàn)在把它們改成:

CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY-2);

CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY+2);

再運(yùn)行程序,結(jié)果如圖8-12所示。圖8-12改變了優(yōu)先級(jí)的運(yùn)行結(jié)果8.4.1程序分析

例8-11中,程序SyncThread1.java完成如下功能:一個(gè)線程完成偶數(shù)的計(jì)算和顯示;另一個(gè)線程完成對(duì)所求偶數(shù)的判斷,并顯示判斷結(jié)果。

【例8-11】SyncThread1.java程序示例。

importjava.awt.*;

importjavax.swing.*;

importjava.awt.event.*;

publicclassSyncThread1extendsJAppletimplementsActionListener{8.4線程同步Eventh1;

Watchth2;

JTextFieldt1=newJTextField(10);

JTextFieldt2=newJTextField(15);

JTextFieldt3=newJTextField(15);

JButtonbt=newJButton("開始統(tǒng)計(jì)");

//線程類的定義:run()方法完成求得偶數(shù)并顯示

classEvenextendsThread{

intcount=0,i=0;

booleanflag=true;publicvoidrun(){

while(true){

i++;

Integer.toString(i);

//沒(méi)有實(shí)際的意義,僅僅為了增加效果

i++;

t1.setText(Integer.toString(i));

//將求得的偶數(shù)顯示在文本框里

try{

sleep(1);

}catch(InterruptedExceptione){}

}

}

publicvoidjudgeEven(){//判斷偶數(shù)

if(i%2!=0){

//將非偶數(shù)顯示在文本框里(t2)

t2.setText(Integer.toString(i));

//將統(tǒng)計(jì)的非偶數(shù)的個(gè)數(shù)信息顯示在文本框里(t3)

t3.setText("Noteven"+Integer.toString(++count)+"times");

flag=false;

}

//將偶數(shù)的信息顯示在文本框里(t3)

if(flag==true)t3.setText("Alwayseveneachtimes.");

}

}//線程類的定義:調(diào)用偶數(shù)判斷的方法

classWatchextendsThread{

publicvoidrun(){

while(true){

th1.judgeEven();

//通過(guò)th1對(duì)象,調(diào)用judgeEven()方法

try{

sleep(1);

}catch(InterruptedExceptione){}

}

}

}

publicvoidinit(){

Containercp=getContentPane();

cp.setLayout(newFlowLayout());

cp.add(t1);

cp.add(t2);

cp.add(t3);

cp.add(bt);

bt.addActionListener(this);

}publicvoidactionPerformed(ActionEvente){

if(th1==null){//創(chuàng)建并啟動(dòng)求偶數(shù)線程

th1=newEven();

th1.start();

}

if(th2==null){//創(chuàng)建并啟動(dòng)偶數(shù)判斷線程

th2=newWatch();

th2.start();

}

}

}

程序運(yùn)行結(jié)果如圖8-13所示。圖8-13SyncThread1.java的運(yùn)行結(jié)果求偶數(shù)方法中的關(guān)鍵步驟如下:

publicvoidrun(){

while(true){

i++;

i++;

t1.setText(Integer.toString(i));

}由上述代碼可知,文本框里的結(jié)果毫無(wú)疑問(wèn)是偶數(shù),即每次循環(huán)體結(jié)束時(shí),i的值肯定是偶數(shù)。但是從結(jié)果上看,i不總是偶數(shù),而且隨著程序的繼續(xù)運(yùn)行,非偶數(shù)的個(gè)數(shù)會(huì)越來(lái)越多。這是什么原因呢?問(wèn)題就出在下面兩條語(yǔ)句上:

i++;

i++;在執(zhí)行這兩條語(yǔ)句前或者執(zhí)行完這兩條語(yǔ)句后,i肯定是偶數(shù)??墒?,當(dāng)線程th1執(zhí)行完第一條i++語(yǔ)句后可能暫停,在這一時(shí)刻,i是奇數(shù)。接著th2線程接著執(zhí)行,對(duì)i進(jìn)行偶數(shù)判斷,意外發(fā)生了。仔細(xì)分析,意外的發(fā)生是由于兩個(gè)線程(th1和th2)在需要訪問(wèn)統(tǒng)一資源i時(shí),th1在不該停的地方停了下來(lái),導(dǎo)致th2取得了錯(cuò)誤數(shù)據(jù)。應(yīng)該的執(zhí)行順序是:th1執(zhí)行完了以后,再允許th2執(zhí)行;同樣,th2顯示了關(guān)于i的信息后,再執(zhí)行th1,引起i值的變化。防止其他線程進(jìn)行某種操作,直到當(dāng)前線程完成此操作,這就稱為同步。在同步機(jī)制中,將多個(gè)線程都要進(jìn)行操作的資源稱為臨界資源,將訪問(wèn)臨界資源的程序段稱為臨界區(qū)。換另外一種說(shuō)法,在某一時(shí)刻,只能允許一個(gè)線程訪問(wèn)對(duì)象的臨界區(qū),這叫做線程的互斥。

Java以提供關(guān)鍵字synchronized的形式,為防止資源沖突提供了內(nèi)置支持。通常在代碼前加上這條鎖語(yǔ)句,就保證了在一段時(shí)間內(nèi)只能有一個(gè)線程運(yùn)行這段代碼。因?yàn)殒i語(yǔ)句產(chǎn)生了一種互相排斥的效果,所以這種機(jī)制常常稱為“互斥量”。

Java解決同步問(wèn)題的方法有兩種:同步方法和同步代碼塊。

另外,程序中有下面的語(yǔ)句:

i++;

Integer.toString(i);

i++;

語(yǔ)句Integer.toString(i);在這里僅僅是為了讓CPU多執(zhí)行一條語(yǔ)句,加大兩條i++語(yǔ)句在時(shí)間上的執(zhí)行空間,使得線程停在它們中間的頻率加大,增強(qiáng)程序的運(yùn)行效果。8.4.2同步方法

同步方法是在可能會(huì)發(fā)生同步問(wèn)題的run()和judgeEven()方法之前加上synchronized關(guān)鍵字,即

publicsynchronizedvoidrun(){

}

publicsynchronizedvoidjudgeEven(){

}這兩個(gè)方法都含在Even類中,程序中的線程在訪問(wèn)它們時(shí)使用同一個(gè)對(duì)象th1。當(dāng)線程訪問(wèn)其中的一個(gè)方法時(shí),就會(huì)給所有的方法加鎖,在加鎖的線程執(zhí)行完畢后,被鎖住的所有方法將自動(dòng)開鎖,這時(shí)才允許別的線程訪問(wèn)同步方法。

按照這種解決方式,程序運(yùn)行結(jié)果如圖8-14所示。圖8-14用同步方法解決的運(yùn)行結(jié)果圖8-14中的兩個(gè)文本框里沒(méi)有顯示任何信息。由于兩個(gè)方法同時(shí)被鎖住,且求偶數(shù)的方法是死循環(huán)(while(true){…})

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 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ì)用戶上傳內(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ì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論