Java的線程處理課件_第1頁
Java的線程處理課件_第2頁
Java的線程處理課件_第3頁
Java的線程處理課件_第4頁
Java的線程處理課件_第5頁
已閱讀5頁,還剩106頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

Java的線程處理 10.1線程的基本概念

我們已經(jīng)對(duì)多任務(wù)非常熟悉,Windows和Linux都是多任務(wù)的操作系統(tǒng)。這些操作系統(tǒng)可以同時(shí)運(yùn)行兩個(gè)或兩個(gè)以上的程式,並且看起來這些程式似乎在同時(shí)運(yùn)行。當(dāng)然,除非你的電腦擁有多個(gè)處理器,否則這些程式是不可能同時(shí)運(yùn)行的。操作系統(tǒng)負(fù)責(zé)把系統(tǒng)資源分配給這些運(yùn)行中的程式,並讓人感覺它們是併發(fā)活動(dòng)的。圖10.1顯示了支持多任務(wù)的操作系統(tǒng)和不支持多任務(wù)的操作系統(tǒng)運(yùn)行程式的情況。圖10.1

實(shí)現(xiàn)多任務(wù)通常有兩種方法,一種稱為搶佔(zhàn)式多任務(wù)(preemptivemultitasking);一種叫合作式多任務(wù)(cooperativemultitasking)。對(duì)於搶佔(zhàn)式多任務(wù),操作系統(tǒng)自行決定何時(shí)中斷一個(gè)程式,將執(zhí)行時(shí)間分給其他程式。相反,對(duì)於合作式多任務(wù)操作系統(tǒng)將與程式進(jìn)行協(xié)商,只有程式自願(yuàn)放棄控制時(shí)才被中斷。雖然搶佔(zhàn)式多任務(wù)實(shí)現(xiàn)起來困難一些,但卻有效得多。對(duì)於合作式多任務(wù)來說,一個(gè)運(yùn)行不好的程式會(huì)佔(zhàn)有整個(gè)系統(tǒng)。

多線程把操作系統(tǒng)的多任務(wù)原理應(yīng)用到程式中,進(jìn)一步發(fā)展了這一原理。應(yīng)用了多線程技術(shù)的程式如同多任務(wù)操作系統(tǒng)一樣,可以同時(shí)執(zhí)行多個(gè)任務(wù)。每個(gè)任務(wù)被稱為一個(gè)線程——它是線程式控制制流的簡(jiǎn)稱。實(shí)際上,多線程的應(yīng)用非常廣泛,例如,流覽器在下載數(shù)據(jù)的同時(shí)還可以流覽其他網(wǎng)頁,或者當(dāng)某個(gè)網(wǎng)頁下載太慢時(shí),還可以控制流覽器中止這個(gè)網(wǎng)頁流覽。Java語言本身也使用一個(gè)線程在後臺(tái)收集無用的記憶體單元——這樣就減少了用戶管理記憶體的麻煩!

通常,我們把操作系統(tǒng)的多個(gè)任務(wù)稱為進(jìn)程(Process),而程式中的多任務(wù)則稱為線程。那麼,線程和進(jìn)程之間有什麼區(qū)別呢?最基本的區(qū)別就是每個(gè)進(jìn)程都擁有一組完整的屬於自己的變數(shù),而線程則共用這些數(shù)據(jù)??雌饋磉@樣似乎不如進(jìn)程安全,確實(shí)如此,本章後面將會(huì)更詳細(xì)地討論。但線程的優(yōu)勢(shì)在於創(chuàng)建和註銷線程的開銷比運(yùn)行新的進(jìn)程少得多,所以現(xiàn)在主流的操作系統(tǒng)都支持多線程。而且,和進(jìn)程間的通信相比,線程間的通信要快得多,也方便得多。10.1.1線程不少程式語言都提供對(duì)線程的支持,同這些語言相比,Java的特點(diǎn)是從最底層開始就對(duì)線程提供支持。除此以外,標(biāo)準(zhǔn)的Java類是可重載的,它允許在一個(gè)給定的應(yīng)用程式中由多個(gè)線程調(diào)用同一方法,而線程彼此之間又互不干擾。Java的這些特點(diǎn)為多線程應(yīng)用程式的設(shè)計(jì)奠定了基礎(chǔ)。究竟什麼是線程呢?正如圖10.2中所示,一個(gè)線程是給定的指令的序列(你所編寫的代碼)、一個(gè)棧(在給定的方法中定義的變數(shù)),以及一些共用數(shù)據(jù)(類一級(jí)的變數(shù))。線程也可以從全局類中訪問靜態(tài)數(shù)據(jù)。圖10.2

每個(gè)線程都有其自己的堆疊和程式計(jì)數(shù)器(PC)。用戶可以把程式計(jì)數(shù)器(PC)設(shè)想為用於跟蹤線程正在執(zhí)行的指令,而堆疊用於跟蹤線程的上下文(上下文是當(dāng)線程執(zhí)行到某處時(shí),當(dāng)前的局部變數(shù)的值)。雖然用戶可以編寫出線上程之間傳送數(shù)據(jù)的副程式,但在正常情況下,一個(gè)線程不能訪問另外一個(gè)線程的棧變數(shù)。

一個(gè)線程或執(zhí)行上下文由三個(gè)主要部分組成:

①一個(gè)虛擬處理機(jī)

②CPU執(zhí)行的代碼

③代碼操作的數(shù)據(jù)代碼可以或不可以由多個(gè)線程共用,這和數(shù)據(jù)是獨(dú)立的。兩個(gè)線程如果執(zhí)行同一個(gè)類的實(shí)例代碼,則它們可以共用相同的代碼。

類似地,數(shù)據(jù)可以或不可以由多個(gè)線程共用,這和代碼是獨(dú)立的。兩個(gè)線程如果共用對(duì)一個(gè)公共對(duì)象的存取,則它們可以共用相同的數(shù)據(jù)。在Java編程中,虛擬處理機(jī)封裝在Thread類的一個(gè)實(shí)例裏。構(gòu)造線程時(shí),定義其上下文的代碼和數(shù)據(jù)是由傳遞給它的構(gòu)造函數(shù)的對(duì)象指定的。10.1.2創(chuàng)建線程在Java平臺(tái)中,創(chuàng)建一個(gè)線程非常簡(jiǎn)單,最直接的方法就是從線程類java.lang.Thread繼承。在缺省情況下,線程類可以被所有的Java應(yīng)用程式調(diào)用。為了使用線程類,我們需要瞭解Thejava.lang.Thread類中定義的五個(gè)方法:●run():該方法用於線程的執(zhí)行。你需要重載該方法,以便讓線程做特定的工作。●start():該方法使得線程啟動(dòng)run()方法?!駍top():該方法同start()方法的作用相反,用於停止線程的運(yùn)行?!駍uspend():該方法同stop()方法不同的是,它並不終止未完成的線程,而只是掛起線程,以後還可恢復(fù)。●resume():該方法重新啟動(dòng)已經(jīng)掛起的線程。下麵我們看一個(gè)通過派生Thread類來創(chuàng)建線程的實(shí)例。例10.1TestThreads.javapublicclassTestThreads{ publicstaticvoidmain(Stringargs[]) { MyThreada=newMyThread("ThreadA"); MyThreadb=newMyThread("ThreadB"); MyThreadc=newMyThread("ThreadC"); a.start(); b.start(); c.start();}}classMyThreadextendsThread{ Stringwhich; MyThread(Stringwhich) { this.which=which; }

publicvoidrun(){

intiterations=(int)(Math.random()*100)%15; intsleepinterval=(int)(Math.random()*1000); System.out.println(which+"runningfor"+ iterations+"iterations"); System.out.println(which+"sleepingfor"+

sleepinterval+"msbetweenloops"); for(inti=0;i<iterations;i++) { System.out.println(which+""+i); try { Thread.sleep(sleepinterval); } catch(InterruptedExceptione) {} }}}這個(gè)例子演示了如何從現(xiàn)有的Thread類中派生出一個(gè)新類。

新創(chuàng)建的類重載了run()方法,但實(shí)現(xiàn)run()方法不必很嚴(yán)格,因?yàn)門hread類可提供一個(gè)缺省的run()方法,儘管它不是特別有用。其運(yùn)行結(jié)果如下:

ThreadArunningfor2iterationsThreadAsleepingfor913msbetweenloopsThreadA0ThreadBrunningfor12iterationsThreadBsleepingfor575msbetweenloopsThreadB0ThreadCrunningfor4iterationsThreadCsleepingfor370msbetweenloopsThreadC0ThreadC1ThreadB1ThreadC2ThreadA1ThreadC3ThreadB2ThreadB3ThreadB4ThreadB5ThreadB6ThreadB7ThreadB8ThreadB9ThreadB10ThreadB1110.1.3使用Runnable介面在不少場(chǎng)合,你不能重新定義類的父母,或者不能定義派生的線程類,但也許你的類的層次要求父類為特定的類,然而,Java語言是不支持多父類的。在這些情況下,可以通過Runnable介面來實(shí)現(xiàn)多線程的功能。實(shí)際上,Thread類本身也實(shí)現(xiàn)了Runnable介面。一個(gè)Runnable介面提供了一個(gè)publicvoidrun()方法。下麵我們來看一個(gè)用Runnable介面創(chuàng)建線程的實(shí)例。例10.2RunnableTest.javapublicclassRunnableTest{ publicstaticvoidmain(Stringargs[]) { Testr=newTest(); Threadt=newThread(r); t.start(); }}classTestimplementsRunnable{ inti;

publicvoidrun() { while(true) { System.out.println("Hello"+i++); if(i==10) break;} }}

上面程式的運(yùn)行結(jié)果非常簡(jiǎn)單,這裏不再列出。使用Runnable介面,需要我們實(shí)現(xiàn)run()方法。我們也需要?jiǎng)?chuàng)建Thread對(duì)象的一個(gè)實(shí)例,它最終是用來調(diào)用run()方法的。首先,main()方法構(gòu)造了Test類的一個(gè)實(shí)例r。實(shí)例r有它自己的數(shù)據(jù),在這裏就是整數(shù)i。因?yàn)閷?shí)例r是傳給Thread的類構(gòu)造函數(shù)的,所以r的整數(shù)i就是線程運(yùn)行時(shí)刻所操作的數(shù)據(jù)。線程總是從它所裝載的Runnable實(shí)例(在本例中,這個(gè)實(shí)例就是r)的run()方法開始運(yùn)行。

一個(gè)多線程編程環(huán)境允許創(chuàng)建基於同一個(gè)Runnable實(shí)例的多個(gè)線程。這可以通過以下方法來做到:

Threadt1=newThread(r); Threadt2=newThread(r);

此時(shí),這兩個(gè)線程共用數(shù)據(jù)和代碼。10.1.4方法的選擇以上例子雖然展示了如何使用Runnable介面創(chuàng)建一個(gè)線程,但是它並不典型。我們說過,使用Runnable結(jié)構(gòu)的主要原因是必須從其他父類繼承。那麼,什麼時(shí)候才是使用Runnable介面的最佳時(shí)機(jī)呢。給定各種方法的選擇,你如何決定使用哪個(gè)?下麵分別列出了選用這兩種方法的幾個(gè)原則。

使用Runnable的原因:●從面向?qū)ο蟮慕嵌葋砜?,Thread類是一個(gè)虛擬處理機(jī)嚴(yán)格的封裝,因此只有當(dāng)處理機(jī)模型修改或擴(kuò)展時(shí),才應(yīng)該繼承類。正因?yàn)檫@個(gè)原因和區(qū)別一個(gè)正在運(yùn)行的線程的處理機(jī)、代碼和數(shù)據(jù)部分的意義,本教程採用了這種方法?!裼伸禞ava技術(shù)只允許單一繼承,所以如果你已經(jīng)繼承了Thread,你就不能再繼承其他任何類,例如Applet。在某些情況下,這會(huì)使你只能採用實(shí)現(xiàn)Runnable的方法。●因?yàn)橛袝r(shí)你必須實(shí)現(xiàn)Runnable,所以你可能喜歡保持一致,並總是使用這種方法。繼承Thread的優(yōu)點(diǎn):●當(dāng)一個(gè)run()方法體現(xiàn)在繼承Thread類的類中,用this指向?qū)嶋H控制運(yùn)行的Thread實(shí)例。因此代碼簡(jiǎn)單了一些,許多Java編程語言的程式員使用擴(kuò)展Thread的機(jī)制。注:如果你採用這種方法,在你的代碼生命週期的後期,單繼承模型可能會(huì)給你帶來困難。下麵的例子中分別使用了兩種方式創(chuàng)建線程,大家可以分析一下原因,以進(jìn)一步理解如何使用這兩個(gè)線程模型。例10.3TimerTest.javaimportjava.awt.*;importjava.awt.event.*;importjavax.swing.*;importjava.util.*;

publicclassTimerTest{publicstaticvoidmain(String[]args){JFramef=newTimerTestFrame();f.show();}}

classTimerTestFrameextendsJFrame{publicTimerTestFrame(){ setSize(450,300);setTitle("TimerTest");

addWindowListener(newWindowAdapter(){ publicvoidwindowClosing(WindowEvente){ System.exit(0);}});

Containerc=getContentPane();c.setLayout(newGridLayout(2,3));c.add(newClockCanvas("SanJose","GMT-8"));c.add(newClockCanvas("Taipei","GMT+8"));c.add(newClockCanvas("Berlin","GMT+1"));c.add(newClockCanvas("NewYork","GMT-5"));c.add(newClockCanvas("Cairo","GMT+2"));c.add(newClockCanvas("Bombay","GMT+5"));

}}interfaceTimerListener{

voidtimeElapsed(Timert);}classTimerextendsThread{privateTimerListenertarget;privateintinterval;

publicTimer(inti,TimerListenert){ target=t;interval=i;setDaemon(true);}

publicvoidrun(){ try{ while(!interrupted()){ sleep(interval);target.timeElapsed(this);}}

catch(InterruptedExceptione){}}}

classClockCanvasextendsJPanelimplementsTimerListener{

privateintseconds=0;privateStringcity;privateintoffset;privateGregorianCalendarcalendar;privatefinalintLOCAL=16;

publicClockCanvas(Stringc,Stringtz){ city=c;calendar=newGregorianCalendar(TimeZone.getTimeZone(tz));Timert=newTimer(1000,this);t.start();setSize(125,125);}

publicvoidpaintComponent(Graphicsg){ super.paintComponent(g);g.drawOval(0,0,100,100);doublehourAngle=2*Math.PI*(seconds-3*60*60)/(12*60*60);doubleminuteAngle=2*Math.PI*(seconds-15*60)/(60*60);doublesecondAngle=2*Math.PI*(seconds-15)/60;g.drawLine(50,50,50+(int)(30*Math.cos(hourAngle)),50+(int)(30*Math.sin(hourAngle)));g.drawLine(50,50,50+(int)(40*Math.cos(minuteAngle)),50+(int)(40*Math.sin(minuteAngle)));g.drawLine(50,50,50+(int)(45*Math.cos(secondAngle)),50+(int)(45*Math.sin(secondAngle)));g.drawString(city,0,115);}

publicvoidtimeElapsed(Timert){ calendar.setTime(newDate());seconds=calendar.get(Calendar.HOUR)*60*60+calendar.get(Calendar.MINUTE)*60+calendar.get(Calendar.SECOND);

repaint();}}

這個(gè)例子實(shí)現(xiàn)了一個(gè)多國(guó)時(shí)間的現(xiàn)實(shí)窗口。程式中,Timer類是直接從Thread類繼承的,而ClockCanvas類是通過實(shí)現(xiàn)Runnable介面來實(shí)現(xiàn)線程的功能的。顯然,這是因?yàn)镃lockCanvas類必須從JPanel類繼承用來畫出時(shí)鐘。程式的運(yùn)行結(jié)果如圖10.3所示。圖10.310.2線程的屬性10.2.1線程的狀態(tài)線程有四種狀態(tài),分別為●new(初始態(tài)):一個(gè)線程在調(diào)用new()方法之後,調(diào)用start()方法之前所處的狀態(tài)。在初始態(tài)中,可以調(diào)用start()和stop()方法?!駌Runnable(可運(yùn)行狀態(tài)):一旦線程調(diào)用了start()方法,線程就轉(zhuǎn)到Runnable()狀態(tài)。注意,如果線程處於Runnable狀態(tài),它也有可能不在運(yùn)行,這是因?yàn)檫€存在優(yōu)先順序和調(diào)度問題?!馼locked(阻塞/掛起狀態(tài)):線程處於阻塞狀態(tài)。這是由兩種可能性造成的:因掛起而暫停;由於某些原因而阻塞,例如等待IO請(qǐng)求的完成等。●dead(終止?fàn)顟B(tài)):線程轉(zhuǎn)到退出狀態(tài)。這有兩種可能性:run()方法執(zhí)行結(jié)束;調(diào)用了stop()方法。

一個(gè)Thread對(duì)象在它的生命週期中會(huì)處於各種不同的狀態(tài)。圖10.4形象地說明了這點(diǎn)。儘管線程變?yōu)榭蛇\(yùn)行的,但它並不立即開始運(yùn)行。在一個(gè)只帶有一個(gè)處理機(jī)的機(jī)器上,某一個(gè)時(shí)刻只能進(jìn)行一個(gè)動(dòng)作。在Java中,線程是搶佔(zhàn)式的,但並不一定是分時(shí)的(一個(gè)常見的概念錯(cuò)誤是認(rèn)為“搶佔(zhàn)式”只不過是“分時(shí)”的一種別稱而已)。搶佔(zhàn)式調(diào)度模型是指可能有多個(gè)線程是可運(yùn)行的,但只有一個(gè)線程在實(shí)際運(yùn)行。

這個(gè)線程會(huì)一直運(yùn)行,直至它不再是可運(yùn)行的,或者另一個(gè)具有更高優(yōu)先順序的線程成為可運(yùn)行的。對(duì)於後面一種情形,則是因低優(yōu)先順序線程被高優(yōu)先順序線程搶佔(zhàn)了運(yùn)行的機(jī)會(huì)。

一個(gè)線程可能因?yàn)楦鞣N原因而不再是可運(yùn)行的:線程的代碼可能執(zhí)行了一個(gè)Thread.sleep()調(diào)用,要求這個(gè)線程暫停一段固定的時(shí)間;這個(gè)線程可能在等待訪問某個(gè)資源,而且在這個(gè)資源可訪問之前,這個(gè)線程無法繼續(xù)運(yùn)行。圖10.4

所有可運(yùn)行線程根據(jù)優(yōu)先順序保存在池中。當(dāng)一個(gè)被阻塞的線程變成可運(yùn)行時(shí),它會(huì)被放回相應(yīng)的可運(yùn)行池。優(yōu)先順序最高的非空池中的線程會(huì)得到處理機(jī)時(shí)間(被運(yùn)行)。因?yàn)镴ava線程不一定是分時(shí)的,所有你必須確保你的代碼中的線程會(huì)不時(shí)地給另外一個(gè)線程運(yùn)行的機(jī)會(huì)。這可以通過在各種時(shí)間間隔中發(fā)出sleep()調(diào)用來做到。來看如下程式段:

publicclassTestimplementsRunnable

{ publicvoidrun(){while(true){ //dolotsofinterestingstuff//Giveotherthreadsachancetry{Thread.sleep(10);}catch(InterruptedExceptione){//Thisthread'ssleepwasinterrupted//byanotherthread} } }}

注意try和catch塊的使用。Thread.sleep()和其他使線程暫停一段時(shí)間的方法是可中斷的。線程可以調(diào)用另外一個(gè)線程的interrupt()方法,這將向暫停的線程發(fā)出一個(gè)InterruptedException。

Thread類的sleep()方法對(duì)當(dāng)前線程操作,因此被稱作Thread.sleep(x),它是一個(gè)靜態(tài)方法。sleep()的參數(shù)指定以毫秒為單位的線程最小休眠時(shí)間,除非線程因?yàn)橹袛喽嵩缁謴?fù)執(zhí)行,否則它不會(huì)在這段時(shí)間之前恢復(fù)執(zhí)行。Thread類的另一個(gè)方法yield(),可以用來使具有相同優(yōu)先順序的線程獲得執(zhí)行的機(jī)會(huì)。如果具有相同優(yōu)先順序的其他線程是可運(yùn)行的,yield()將把調(diào)用線程放到可運(yùn)行池中並使另一個(gè)線程運(yùn)行。如果沒有相同優(yōu)先順序的可運(yùn)行進(jìn)程,yield()什麼都不做。

sleep()調(diào)用會(huì)給較低優(yōu)先順序線程一個(gè)運(yùn)行的機(jī)會(huì)。yield()方法只會(huì)給相同優(yōu)先順序線程一個(gè)執(zhí)行的機(jī)會(huì)。10.2.2線程的調(diào)度到目前為止,我們已經(jīng)學(xué)習(xí)了創(chuàng)建和管理線程的基本知識(shí)。你需要做的就是啟動(dòng)一個(gè)線程,並讓它運(yùn)行。你的應(yīng)用程式也許希望等待一個(gè)線程執(zhí)行完畢,也許打算發(fā)送一個(gè)資訊給線程,或者只打算讓線程在處理之前休眠一會(huì)兒。線程類提供了四種對(duì)線程進(jìn)行操作的重要方法:sleep()、join()、wait()和notify()。sleep()方法是使線程停止一段時(shí)間的方法。在sleep時(shí)間間隔期滿後,線程不一定立即恢復(fù)執(zhí)行。這是因?yàn)樵谀莻€(gè)時(shí)刻,其他線程可能正在運(yùn)行而且沒有被調(diào)度為放棄執(zhí)行,除非:

(a)“醒來”的線程具有更高的優(yōu)先順序;

(b)正在運(yùn)行的線程因?yàn)槠渌蚨枞?/p>

如果一個(gè)應(yīng)用程式需要執(zhí)行很多時(shí)間,比如一個(gè)耗時(shí)很長(zhǎng)的計(jì)算工作,你可以把該計(jì)算工作設(shè)計(jì)成線程。但是,假定還有另外一個(gè)線程需要計(jì)算結(jié)果,當(dāng)計(jì)算結(jié)果出來後,如何讓那個(gè)線程知道計(jì)算結(jié)果呢?解決該問題的一個(gè)方法是讓第二個(gè)線程一直不停地檢查一些變數(shù)的狀態(tài),直到這些變數(shù)的狀態(tài)發(fā)生改變。這樣的方式在Unix風(fēng)格的伺服器中常常用到。Java提供了一個(gè)更加簡(jiǎn)單的機(jī)制,即線程類中的join()方法。join()方法使得一個(gè)線程等待另外一個(gè)線程結(jié)束後再執(zhí)行。例如,一個(gè)GUI(或者其他線程)使用join()方法等待一個(gè)子線程執(zhí)行完畢:

CompleteCalcThreadt=newCompleteCalcThread(); t.start(); //做一會(huì)兒其他的事情//然後等待

t.join(); //使用計(jì)算結(jié)果……join()方法有三種格式:●voidjoin():等待線程執(zhí)行完畢?!駐oidjoin(longtimeout):最多等待某段時(shí)間讓線程完成。●voidjoin(longmilliseconds,

intnanoseconds):最多等待某段時(shí)間(毫秒+納秒),讓線程完成。

線程APIisAlive()同join()相關(guān)聯(lián)時(shí),是很有用的。一個(gè)線程在start(此時(shí)run()方法已經(jīng)啟動(dòng))之後,在stop之前的某時(shí)刻處於isAlive狀態(tài)。對(duì)於編寫線程的程式員來說,還有其他兩個(gè)有用的方法,即wait()和notify()。使用這兩個(gè)API,我們可以精確地控制線程的執(zhí)行過程。關(guān)於這兩個(gè)方法的使用,將在後面詳細(xì)解釋。10.2.3線程的優(yōu)先順序線程可以設(shè)定優(yōu)先順序,高優(yōu)先順序的線程可以安排在低優(yōu)先順序線程之前完成。一個(gè)應(yīng)用程式可以通過使用線程中的setPriority(int)方法來設(shè)置線程的優(yōu)先順序大小。對(duì)於多線程程式,每個(gè)線程的重要程度是不盡相同的,如多個(gè)線程在等待獲得CPU時(shí)間時(shí),往往需要優(yōu)先順序高的線程優(yōu)先搶佔(zhàn)到CPU時(shí)間得以執(zhí)行;又如多個(gè)線程交替執(zhí)行時(shí),優(yōu)先順序決定了級(jí)別高的線程得到CPU的次數(shù)多一些且時(shí)間長(zhǎng)一些。這樣,高優(yōu)先順序的線程處理的任務(wù)效率就高一些。Java中,線程的優(yōu)先順序從低到高以整數(shù)1~10表示,共分為10級(jí)。設(shè)置優(yōu)先順序是通過調(diào)用線程對(duì)象的setPriority()方法來進(jìn)行的。設(shè)置優(yōu)先順序的語句為

Threadthreadone=newThread();

//用Thread類的子類創(chuàng)建線程

Threadthreadtwo=newThread();

threadone.setPriority(6);//設(shè)置threadone的優(yōu)先順序?yàn)?

threadtwo.setPriority(3);//設(shè)置threadtwo的優(yōu)先順序?yàn)?

threadone.start();threadtwo.start();//strat()方法啟動(dòng)線程這樣,線程threadone將會(huì)優(yōu)先於線程threadtwo執(zhí)行,並將佔(zhàn)有更多的CPU時(shí)間。該例中,優(yōu)先順序設(shè)置放在線程啟動(dòng)前。也可以在啟動(dòng)後進(jìn)行優(yōu)先順序設(shè)置,以滿足不同的優(yōu)先順序需求。10.3線程組

通常,一個(gè)程式可能包含若干線程,如何來管理這些線程呢?把這些線程按功能分類是個(gè)不錯(cuò)的辦法。Java語言提供了線程組,線程組可以讓你同時(shí)控制一組線程。實(shí)際上,線程組就是一種可以管理一組線程的類。

可以用構(gòu)造方法ThreadGroup()來構(gòu)造一個(gè)線程組,如下所示:

StringgrounName=…; ThreadGroupg=newThreadGroup(groupName);

ThreadGroup()方法的參數(shù)表示一個(gè)線程組,因此該串參數(shù)必須是惟一的。也可以用Thread類的構(gòu)造方法往一個(gè)指定的線程組裏添加新的線程:

Threadt=newThread(g,threadName);

activeCount()方法用於檢測(cè)某個(gè)指定線程組是否有線程處於活動(dòng)狀態(tài):

if(g.activeCount()==0) {//線程g的所有線程都已停止}

要中斷一個(gè)線程組中的所有線程,可以調(diào)用ThreadGroup類的方法interrupt():

errupt();

線程組可以嵌套,即線程組可以擁有子線程組。缺省時(shí),一個(gè)新創(chuàng)建的線程或線程組都屬於當(dāng)前線程組所屬的線程組。線程組的常用方法如下:●ThreadGroup(Stringname):創(chuàng)建一個(gè)新線程組,它的父線程組是當(dāng)前線程組?!馮hreadGroup(ThreadGroupparent,Stringname):創(chuàng)建一個(gè)新線程組,其父線程組由parent參數(shù)指定。●intactiveCount():返回當(dāng)前線程組活動(dòng)線程的上限。●intenumerate(Thread[]list):得到當(dāng)前線程組各個(gè)活動(dòng)線程的地址?!馮hreadGroupgetParent():得到當(dāng)前線程組的父線程組。●Voidinterrupt():中斷線程組中所有線程及其子線程組中所有線程。10.4多線程程式的開發(fā)10.4.1synchronized的基本概念關(guān)鍵字synchronized提供Java編程語言一種機(jī)制,允許程式員控制共用數(shù)據(jù)的線程。本節(jié)重點(diǎn)討論其使用方法。我們已經(jīng)知道,進(jìn)程允許兩個(gè)或者更多個(gè)線程同時(shí)執(zhí)行。實(shí)際上,這些線程也可以共用對(duì)象和數(shù)據(jù),但在這種情形下,不同的線程在同一時(shí)間內(nèi)不能存取同一數(shù)據(jù),這是因?yàn)樵陂_始設(shè)計(jì)Java的時(shí)候,就採用了線程的概念。Java語言定義了一個(gè)特殊的關(guān)鍵字synchronized(同步),該關(guān)鍵字可以應(yīng)用到代碼塊上(代碼塊也包括入口方法)。該關(guān)鍵字的目的是防止多個(gè)線程在同一時(shí)間執(zhí)行同一代碼塊內(nèi)的代碼。定義一個(gè)同步方法的格式如下:

[public|private]synchronized{type}

methodname(...)

一個(gè)簡(jiǎn)單的應(yīng)用例子如下:

publicclasssomeClass

{ publicvoidaMethod(){ ... synchronized(this){ //Synchronizedcodeblock}...}}

同步化的關(guān)鍵字可以保證在同一時(shí)間內(nèi)只有一個(gè)線程可以執(zhí)行某代碼段,而任何其他要用到該段代碼的線程將被阻塞,直到第一個(gè)線程執(zhí)行完該段代碼,如圖10.5所示對(duì)象鎖標(biāo)誌synchronized到底是如何做到保證資源訪問同步的呢?在Java技術(shù)中,每個(gè)對(duì)象都有一個(gè)和它相關(guān)聯(lián)的標(biāo)誌。這個(gè)標(biāo)誌可以被認(rèn)為是“鎖標(biāo)誌”。synchronized關(guān)鍵字能保證多線程之間的同步運(yùn)行,即允許獨(dú)佔(zhàn)地存取對(duì)象。當(dāng)線程運(yùn)行到synchronized語句時(shí),它檢查作為參數(shù)傳遞的對(duì)象,並在繼續(xù)執(zhí)行之前試圖從對(duì)象獲得鎖標(biāo)誌。圖10.5

意識(shí)到它自身並沒有保護(hù)數(shù)據(jù)是很重要的。因?yàn)槿绻粋€(gè)對(duì)象的pop()方法沒有受到synchronized的影響,且pop()是由另一個(gè)線程調(diào)用的,那麼仍然存在破壞data的一致性的危險(xiǎn)。如果要使鎖有效,所有存取共用數(shù)據(jù)的方法必須在同一把鎖上同步。圖10.6顯示了如果pop()受到synchronized的影響,且另一個(gè)線程在原線程持有那個(gè)對(duì)象的鎖時(shí)試圖執(zhí)行pop()方法時(shí)所發(fā)生的事情:圖10.6

當(dāng)線程試圖執(zhí)行synchronized(this)語句時(shí),它試圖從this對(duì)象獲取鎖標(biāo)誌。由於得不到標(biāo)誌,所以線程不能繼續(xù)運(yùn)行。然後,線程加入到與那個(gè)對(duì)象鎖相關(guān)聯(lián)的等待線程池中。當(dāng)標(biāo)誌返回給對(duì)象時(shí),某個(gè)等待這個(gè)標(biāo)誌的線程將得到這把鎖並繼續(xù)運(yùn)行。由於等待一個(gè)對(duì)象的鎖標(biāo)誌的線程在得到標(biāo)誌之前不能恢復(fù)運(yùn)行,所以讓持有鎖標(biāo)誌的線程在不再需要的時(shí)候返回標(biāo)誌是很重要的。

鎖標(biāo)誌將自動(dòng)返回給它的對(duì)象。持有鎖標(biāo)誌的線程執(zhí)行到synchronized()代碼塊末尾時(shí)將釋放鎖。Java技術(shù)特別注意了保證即使出現(xiàn)中斷或異常而使得執(zhí)行流跳出synchronized()代碼塊,鎖也會(huì)自動(dòng)返回。此外,如果一個(gè)線程對(duì)同一個(gè)對(duì)象兩次發(fā)出synchronized調(diào)用,則在跳出最外層的塊時(shí),標(biāo)誌會(huì)正確地釋放,而最內(nèi)層的將被忽略。這些規(guī)則使得與其他系統(tǒng)中的等價(jià)功能相比,管理同步塊的使用簡(jiǎn)單了很多。10.4.2多線程的控制線程有兩個(gè)缺陷:死鎖和饑餓。所謂死鎖,就是一個(gè)或者多個(gè)線程,在一個(gè)給定的任務(wù)中,協(xié)同作用,互相干涉,從而導(dǎo)致一個(gè)或者更多線程永遠(yuǎn)等待下去。與此類似,所謂饑餓,就是一個(gè)線程永久性地佔(zhàn)有資源,使得其他線程得不到該資源。

首先我們看一下死鎖的問題。一個(gè)簡(jiǎn)單的例子就是:你到ATM機(jī)上取錢,卻看到如下的資訊“現(xiàn)在沒有現(xiàn)金,請(qǐng)等會(huì)兒再試?!?,你需要錢,所以你就等了一會(huì)兒再試,但是你又看到了同樣的資訊;與此同時(shí),在你後面,一輛運(yùn)款車正等待著把錢放進(jìn)ATM機(jī)中,但是運(yùn)款車到不了ATM取款機(jī),因?yàn)槟愕钠嚀踔馈T谶@種情況下,就發(fā)生了所謂的死鎖。

在饑餓的情形下,系統(tǒng)並不處於死鎖狀態(tài)中,因?yàn)橛幸粋€(gè)進(jìn)程仍在處理之中,只是其他進(jìn)程永遠(yuǎn)得不到執(zhí)行的機(jī)會(huì)而已。在什麼樣的環(huán)境下,會(huì)導(dǎo)致饑餓的發(fā)生,並沒有預(yù)先確定好的規(guī)則。但一旦發(fā)生下麵四種情況之一,就會(huì)導(dǎo)致死鎖的發(fā)生?!裣嗷ヅ懦猓阂粋€(gè)線程或者進(jìn)程永遠(yuǎn)佔(zhàn)有一共享資源,例如,獨(dú)佔(zhàn)該資源?!褶捜Φ却哼M(jìn)程A等待進(jìn)程B,而後者又在等待進(jìn)程C,而進(jìn)程C又在等待進(jìn)程A?!癫糠址峙洌嘿Y源被部分分配。例如,進(jìn)程A和B都需要訪問一個(gè)檔,並且都要用到印表機(jī),進(jìn)程A獲得了檔資源,進(jìn)程B獲得了印表機(jī)資源?!袢鄙賰?yōu)先權(quán):一個(gè)進(jìn)程訪問了某個(gè)資源,但是一直不釋放該資源,即使該進(jìn)程處於阻塞狀態(tài)。為了避免出現(xiàn)死鎖的情況,我們就必須在多線程程式中做同步管理,為此必須編寫使它們交互的程式。java.lang.Object類中提供了兩個(gè)用於線程通信的方法:wait()和notify()。如果線程對(duì)一個(gè)同步對(duì)象x發(fā)出一個(gè)wait()調(diào)用,則該線程會(huì)暫停執(zhí)行,直到另一個(gè)線程對(duì)同一個(gè)同步對(duì)象x也發(fā)出一個(gè)wait()調(diào)用。為了讓線程對(duì)一個(gè)對(duì)象調(diào)用wait()或notify(),線程必須鎖定那個(gè)特定的對(duì)象。也就是說,只能在它們被調(diào)用的實(shí)例的同步塊內(nèi)使用wait()和notify()。當(dāng)某個(gè)線程執(zhí)行包含對(duì)一個(gè)特定對(duì)象執(zhí)行wait()調(diào)用的同步代碼時(shí),這個(gè)線程就被放到與那個(gè)對(duì)象相關(guān)的等待池中。此外,調(diào)用wait()的線程自動(dòng)釋放對(duì)象的鎖標(biāo)誌。

對(duì)一個(gè)特定對(duì)象執(zhí)行notify()調(diào)用時(shí),將從對(duì)象的等待池中移走一個(gè)任意的線程,並放到鎖池中。鎖池中的對(duì)象一直在等待,直到可以獲得對(duì)象的鎖標(biāo)記。notifyAll()方法將從等待池中移走所有等待那個(gè)對(duì)象的線程,並把它們放到鎖池中。只有鎖池中的線程能獲取對(duì)象的鎖標(biāo)記,鎖標(biāo)記允許線程從上次因調(diào)用wait()而中斷的地方開始繼續(xù)運(yùn)行。

在許多實(shí)現(xiàn)了wait()/notify()機(jī)制的系統(tǒng)中,醒來的線程必定是那個(gè)等待時(shí)間最長(zhǎng)的線程。然而,在Java技術(shù)中,並不能保證這點(diǎn)。應(yīng)注意的是,不管是否有線程在等待,用戶都可以調(diào)用notify()。如果對(duì)一個(gè)對(duì)象調(diào)用notify()方法,而在這個(gè)對(duì)象的鎖標(biāo)記等待池中並沒有阻塞的線程,那麼調(diào)用notify()將不起任何作用。對(duì)notify()的調(diào)用不會(huì)被存儲(chǔ)。

下麵給出一個(gè)線程交互的實(shí)例,用於說明如何使用synchronized關(guān)鍵字和wait()、notify()方法來解決線程的同步問題。例10.4DemoThread.javaimportjava.lang.Runnable;importjava.lang.Thread;

publicclassDemoThreadimplementsRunnable

{publicDemoThread() { TestThreadtestthread1=newTestThread(this,"1"); TestThreadtestthread2=newTestThread(this,"2"); testthread2.start(); testthread1.start(); }

publicstaticvoidmain(String[]args){

DemoThreaddemoThread1=newDemoThread(); }

publicvoidrun() { TestThreadt=(TestThread)Thread.currentThread(); try { if(!t.getName().equalsIgnoreCase("1")){

synchronized(this) { wait(); } }

while(true) {System.out.println("@timeinthread"+ t.getName()+"="+t.increaseTime()); if(t.getTime()%10==0) { synchronized(this) { System.out.println("********************************"); notify(); if(t.getTime()==100) break; wait(); } } } } catch(Exceptione) { e.printStackTrace(); } }}classTestThreadextendsThread{privateinttime=0;

publicTestThread(Runnabler,Stringname){ super(r,name);}

publicintgetTime(){ returntime;}

publicintincreaseTime(){ return++time;}}

本例實(shí)現(xiàn)了兩個(gè)線程,每個(gè)線程輸出1~100的數(shù)字。工作程式是:第一個(gè)線程輸出1~10,停止,通知第二個(gè)線程;第二個(gè)線程輸出1~10,停止,再通知第一個(gè)線程;第一個(gè)線程輸出11~20……

在Java中,每個(gè)對(duì)象都有個(gè)對(duì)象鎖標(biāo)誌(Objectlockflag)與之想關(guān)聯(lián),當(dāng)一個(gè)線程A調(diào)用對(duì)象的一段synchronized代碼時(shí),它首先要獲取與這個(gè)對(duì)象關(guān)聯(lián)的對(duì)象鎖標(biāo)誌,然後才執(zhí)行相應(yīng)的代碼,執(zhí)行結(jié)束後,把這個(gè)對(duì)象鎖標(biāo)誌返回給對(duì)象。因此,線上程A執(zhí)行synchronized代碼期間,如果另一個(gè)線程B也要執(zhí)行同一對(duì)象的一段synchronized代碼(不一定與線程A執(zhí)行的相同),那麼它必須等到線程A執(zhí)行完後,才能繼續(xù)。

在synchronized代碼被執(zhí)行期間,線程可以調(diào)用對(duì)象的wait()方法,釋放對(duì)象鎖標(biāo)誌,進(jìn)入等待狀態(tài),並且可以調(diào)用notify()或者notifyAll()方法通知正在等待的其他線程。notify()方法通知等待佇列中的第一個(gè)線程,notifyAll()方法通知的是等待佇列中的所有線程。程式的部分輸出結(jié)果如下:

@timeinthread1=1@timeinthread1=2@timeinthread1=3@timeinthread1=4@timeinthread1=5@timeinthread1=6@timeinthread1=7@timeinthread1=8@timeinthread1=9@timeinthread1=10********************************@timeinthread2=1@timeinthread2=2@timeinthread2=3@timeinthread2=4@timeinthread2=5@timeinthread2=6@timeinthread2=7@timeinthread2=8@timeinthread2=9@timeinthread2=10********************************

線程中還有三個(gè)控制線程執(zhí)行的方法:suspend()、resume()和stop()方法。但從JDK1.2開始,Java標(biāo)準(zhǔn)不贊成使用它們來控制線程的執(zhí)行,而是用wait()和notify()來代替它們。這裏我們只對(duì)這三個(gè)方法做個(gè)簡(jiǎn)單地介紹?!駍uspend()和resume()方法

resume()方法的惟一作用就是恢復(fù)被掛起的線程。所以,如果沒有suspend(),resume()也就沒有存在的必要。從設(shè)計(jì)的角度來看,有兩個(gè)原因使得使用suspend()非常危險(xiǎn):它容易產(chǎn)生死鎖;它允許一個(gè)線程式控制制另一個(gè)線程代碼的執(zhí)行。下麵分別介紹這兩種危險(xiǎn)。假設(shè)有兩個(gè)線程:threadA和threadB。當(dāng)正在執(zhí)行它們的代碼時(shí),threadB獲得一個(gè)對(duì)象的鎖,然後繼續(xù)它的任務(wù)。當(dāng)threadA的執(zhí)行代碼調(diào)用threadB.suspend()時(shí),將使threadB停止執(zhí)行它的代碼。

如果threadB.suspend()沒有使threadB釋放它所持有的鎖,就會(huì)發(fā)生死鎖。如果調(diào)用threadB.resume()的線程需要threadB仍持有的鎖,這兩個(gè)線程就會(huì)陷入死鎖。假設(shè)threadA調(diào)用threadB.suspend()。如果threadB被掛起時(shí)threadA獲得控制,那麼threadB就永遠(yuǎn)得不到機(jī)會(huì)來進(jìn)行清除工作,例如使它正在操作的共用數(shù)據(jù)處於穩(wěn)定狀態(tài)。為了安全起見,只有threadB才可以決定何時(shí)停止它自己的代碼。

你應(yīng)該使用對(duì)同步對(duì)象調(diào)用wait()和notify()的機(jī)制來代替suspend()和resume()進(jìn)行線程式控制制。這種方法是通過執(zhí)行wait()調(diào)用來強(qiáng)制線程決定何時(shí)“掛起”自己。這使得同步對(duì)象的鎖被自動(dòng)釋放,並給予線程一個(gè)在調(diào)用wait()之前穩(wěn)定任何數(shù)據(jù)的機(jī)會(huì)?!駍top()方法

stop()方法的情形是類似的,但結(jié)果有所不同。如果一個(gè)線程在持有一個(gè)對(duì)象鎖的時(shí)候被停止,它將在終止之前釋放它持有的鎖。這避免了前面所討論的死鎖問題,但它又引入了其他問題。

在前面的範(fàn)例中,如果線程在已將字元加入棧但還沒有使下標(biāo)值加1之後被停止,則在釋放鎖的時(shí)候會(huì)得到一個(gè)不一致的棧結(jié)構(gòu)??倳?huì)有一些關(guān)鍵操作需要不可分割地執(zhí)行,而且線上程執(zhí)行這些操作時(shí)被停止就會(huì)破壞操作的不可分割性。

一個(gè)關(guān)於停止線程的獨(dú)立而又重要的問題涉及線程的總體設(shè)計(jì)策略。創(chuàng)建線程來執(zhí)行某個(gè)特定作業(yè),並存活於整個(gè)程式的生命週期。換言之,你不會(huì)這樣來設(shè)計(jì)程式:隨意地創(chuàng)建和處理線程,或創(chuàng)建無數(shù)個(gè)對(duì)話框或socket端點(diǎn)。每個(gè)線程都會(huì)消耗系統(tǒng)資源,而系統(tǒng)資源並不是無限的。這並不是暗示一個(gè)線程必須連續(xù)執(zhí)行;它只是簡(jiǎn)單地意味著應(yīng)當(dāng)使用合適而安全的wait()和notify()機(jī)制來控制線程。10.4.3多線程之間的通信

Java語言提供了各種各樣的輸入/輸出流,使我們能夠很方便地對(duì)數(shù)據(jù)進(jìn)行操作。其中,管道(Pipe)流是一種特殊的流,用於在不同線程間直接傳送數(shù)據(jù)(一個(gè)線程發(fā)送數(shù)據(jù)到輸出管道,另一個(gè)線程從輸入管道中讀數(shù)據(jù))。通過使用管道,就可實(shí)現(xiàn)不同線程間的通信,無需求助於類似臨時(shí)檔之類的東西。Java提供了兩個(gè)特殊的、專門的類用於處理管道,它們就是PipedInputStream類和PipedOutputStream類。PipedInputStream代表了數(shù)據(jù)在管道中的輸出端,也就是線程向管道讀數(shù)據(jù)的一端;PipedOutputStream代表了數(shù)據(jù)在管道中的輸入端,也就是線程向管道寫數(shù)據(jù)的一端,這兩個(gè)類一起使用可以提供數(shù)據(jù)的管道流。為了創(chuàng)建一個(gè)管道流,我們必須首先創(chuàng)建一個(gè)PipedOutStream對(duì)象,然後創(chuàng)建PipedInputStream對(duì)象,前面我們已經(jīng)介紹過。

一旦創(chuàng)建了一個(gè)管道,就可以像操作檔一樣對(duì)管道進(jìn)行數(shù)據(jù)的讀/寫。下麵我們來看一個(gè)運(yùn)用管道實(shí)現(xiàn)線程間通信的實(shí)例。例10.5PipeTest.javaimportjava.util.*;importjava.io.*;

publicclassPipeTest

{

publicstaticvoidmain(Stringargs[]){

try{ /*setuppipes*/PipedOutputStreampout1=newPipedOutputStream();PipedInputStreampin1=newPipedInputStream(pout1)

PipedOutputStreampout2=newPipedOutputStream();Pi

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(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)論