![Java的線程處理課件_第1頁](http://file4.renrendoc.com/view2/M01/31/14/wKhkFma8PHeAaM0QAAByyTVmT54486.jpg)
![Java的線程處理課件_第2頁](http://file4.renrendoc.com/view2/M01/31/14/wKhkFma8PHeAaM0QAAByyTVmT544862.jpg)
![Java的線程處理課件_第3頁](http://file4.renrendoc.com/view2/M01/31/14/wKhkFma8PHeAaM0QAAByyTVmT544863.jpg)
![Java的線程處理課件_第4頁](http://file4.renrendoc.com/view2/M01/31/14/wKhkFma8PHeAaM0QAAByyTVmT544864.jpg)
![Java的線程處理課件_第5頁](http://file4.renrendoc.com/view2/M01/31/14/wKhkFma8PHeAaM0QAAByyTVmT544865.jpg)
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
Java的線程處理 10.1線程的基本概念
我們已經對多任務非常熟悉,Windows和Linux都是多任務的操作系統(tǒng)。這些操作系統(tǒng)可以同時運行兩個或兩個以上的程序,并且看起來這些程序似乎在同時運行。當然,除非你的計算機擁有多個處理器,否則這些程序是不可能同時運行的。操作系統(tǒng)負責把系統(tǒng)資源分配給這些運行中的程序,并讓人感覺它們是并發(fā)活動的。圖10.1顯示了支持多任務的操作系統(tǒng)和不支持多任務的操作系統(tǒng)運行程序的情況。圖10.1
實現多任務通常有兩種方法,一種稱為搶占式多任務(preemptivemultitasking);一種叫合作式多任務(cooperativemultitasking)。對于搶占式多任務,操作系統(tǒng)自行決定何時中斷一個程序,將執(zhí)行時間分給其他程序。相反,對于合作式多任務操作系統(tǒng)將與程序進行協(xié)商,只有程序自愿放棄控制時才被中斷。雖然搶占式多任務實現起來困難一些,但卻有效得多。對于合作式多任務來說,一個運行不好的程序會占有整個系統(tǒng)。
多線程把操作系統(tǒng)的多任務原理應用到程序中,進一步發(fā)展了這一原理。應用了多線程技術的程序如同多任務操作系統(tǒng)一樣,可以同時執(zhí)行多個任務。每個任務被稱為一個線程——它是線程控制流的簡稱。實際上,多線程的應用非常廣泛,例如,瀏覽器在下載數據的同時還可以瀏覽其他網頁,或者當某個網頁下載太慢時,還可以控制瀏覽器中止這個網頁瀏覽。Java語言本身也使用一個線程在后臺收集無用的內存單元——這樣就減少了用戶管理內存的麻煩!
通常,我們把操作系統(tǒng)的多個任務稱為進程(Process),而程序中的多任務則稱為線程。那么,線程和進程之間有什么區(qū)別呢?最基本的區(qū)別就是每個進程都擁有一組完整的屬于自己的變量,而線程則共享這些數據??雌饋磉@樣似乎不如進程安全,確實如此,本章后面將會更詳細地討論。但線程的優(yōu)勢在于創(chuàng)建和注銷線程的開銷比運行新的進程少得多,所以現在主流的操作系統(tǒng)都支持多線程。而且,和進程間的通信相比,線程間的通信要快得多,也方便得多。10.1.1線程不少程序語言都提供對線程的支持,同這些語言相比,Java的特點是從最底層開始就對線程提供支持。除此以外,標準的Java類是可重載的,它允許在一個給定的應用程序中由多個線程調用同一方法,而線程彼此之間又互不干擾。Java的這些特點為多線程應用程序的設計奠定了基礎。究竟什么是線程呢?正如圖10.2中所示,一個線程是給定的指令的序列(你所編寫的代碼)、一個棧(在給定的方法中定義的變量),以及一些共享數據(類一級的變量)。線程也可以從全局類中訪問靜態(tài)數據。圖10.2
每個線程都有其自己的堆棧和程序計數器(PC)。用戶可以把程序計數器(PC)設想為用于跟蹤線程正在執(zhí)行的指令,而堆棧用于跟蹤線程的上下文(上下文是當線程執(zhí)行到某處時,當前的局部變量的值)。雖然用戶可以編寫出在線程之間傳送數據的子程序,但在正常情況下,一個線程不能訪問另外一個線程的棧變量。
一個線程或執(zhí)行上下文由三個主要部分組成:
①一個虛擬處理機
②CPU執(zhí)行的代碼
③代碼操作的數據代碼可以或不可以由多個線程共享,這和數據是獨立的。兩個線程如果執(zhí)行同一個類的實例代碼,則它們可以共享相同的代碼。
類似地,數據可以或不可以由多個線程共享,這和代碼是獨立的。兩個線程如果共享對一個公共對象的存取,則它們可以共享相同的數據。在Java編程中,虛擬處理機封裝在Thread類的一個實例里。構造線程時,定義其上下文的代碼和數據是由傳遞給它的構造函數的對象指定的。10.1.2創(chuàng)建線程在Java平臺中,創(chuàng)建一個線程非常簡單,最直接的方法就是從線程類java.lang.Thread繼承。在缺省情況下,線程類可以被所有的Java應用程序調用。為了使用線程類,我們需要了解Thejava.lang.Thread類中定義的五個方法:●run():該方法用于線程的執(zhí)行。你需要重載該方法,以便讓線程做特定的工作?!駍tart():該方法使得線程啟動run()方法?!駍top():該方法同start()方法的作用相反,用于停止線程的運行?!駍uspend():該方法同stop()方法不同的是,它并不終止未完成的線程,而只是掛起線程,以后還可恢復?!駌esume():該方法重新啟動已經掛起的線程。下面我們看一個通過派生Thread類來創(chuàng)建線程的實例。例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) {} }}}這個例子演示了如何從現有的Thread類中派生出一個新類。
新創(chuàng)建的類重載了run()方法,但實現run()方法不必很嚴格,因為Thread類可提供一個缺省的run()方法,盡管它不是特別有用。其運行結果如下:
ThreadArunningfor2iterationsThreadAsleepingfor913msbetweenloopsThreadA0ThreadBrunningfor12iterationsThreadBsleepingfor575msbetweenloopsThreadB0ThreadCrunningfor4iterationsThreadCsleepingfor370msbetweenloopsThreadC0ThreadC1ThreadB1ThreadC2ThreadA1ThreadC3ThreadB2ThreadB3ThreadB4ThreadB5ThreadB6ThreadB7ThreadB8ThreadB9ThreadB10ThreadB1110.1.3使用Runnable接口在不少場合,你不能重新定義類的父母,或者不能定義派生的線程類,但也許你的類的層次要求父類為特定的類,然而,Java語言是不支持多父類的。在這些情況下,可以通過Runnable接口來實現多線程的功能。實際上,Thread類本身也實現了Runnable接口。一個Runnable接口提供了一個publicvoidrun()方法。下面我們來看一個用Runnable接口創(chuàng)建線程的實例。例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;} }}
上面程序的運行結果非常簡單,這里不再列出。使用Runnable接口,需要我們實現run()方法。我們也需要創(chuàng)建Thread對象的一個實例,它最終是用來調用run()方法的。首先,main()方法構造了Test類的一個實例r。實例r有它自己的數據,在這里就是整數i。因為實例r是傳給Thread的類構造函數的,所以r的整數i就是線程運行時刻所操作的數據。線程總是從它所裝載的Runnable實例(在本例中,這個實例就是r)的run()方法開始運行。
一個多線程編程環(huán)境允許創(chuàng)建基于同一個Runnable實例的多個線程。這可以通過以下方法來做到:
Threadt1=newThread(r); Threadt2=newThread(r);
此時,這兩個線程共享數據和代碼。10.1.4方法的選擇以上例子雖然展示了如何使用Runnable接口創(chuàng)建一個線程,但是它并不典型。我們說過,使用Runnable結構的主要原因是必須從其他父類繼承。那么,什么時候才是使用Runnable接口的最佳時機呢。給定各種方法的選擇,你如何決定使用哪個?下面分別列出了選用這兩種方法的幾個原則。
使用Runnable的原因:●從面向對象的角度來看,Thread類是一個虛擬處理機嚴格的封裝,因此只有當處理機模型修改或擴展時,才應該繼承類。正因為這個原因和區(qū)別一個正在運行的線程的處理機、代碼和數據部分的意義,本教程采用了這種方法?!裼捎贘ava技術只允許單一繼承,所以如果你已經繼承了Thread,你就不能再繼承其他任何類,例如Applet。在某些情況下,這會使你只能采用實現Runnable的方法?!褚驗橛袝r你必須實現Runnable,所以你可能喜歡保持一致,并總是使用這種方法。繼承Thread的優(yōu)點:●當一個run()方法體現在繼承Thread類的類中,用this指向實際控制運行的Thread實例。因此代碼簡單了一些,許多Java編程語言的程序員使用擴展Thread的機制。注:如果你采用這種方法,在你的代碼生命周期的后期,單繼承模型可能會給你帶來困難。下面的例子中分別使用了兩種方式創(chuàng)建線程,大家可以分析一下原因,以進一步理解如何使用這兩個線程模型。例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();}}
這個例子實現了一個多國時間的現實窗口。程序中,Timer類是直接從Thread類繼承的,而ClockCanvas類是通過實現Runnable接口來實現線程的功能的。顯然,這是因為ClockCanvas類必須從JPanel類繼承用來畫出時鐘。程序的運行結果如圖10.3所示。圖10.310.2線程的屬性10.2.1線程的狀態(tài)線程有四種狀態(tài),分別為●new(初始態(tài)):一個線程在調用new()方法之后,調用start()方法之前所處的狀態(tài)。在初始態(tài)中,可以調用start()和stop()方法?!駌Runnable(可運行狀態(tài)):一旦線程調用了start()方法,線程就轉到Runnable()狀態(tài)。注意,如果線程處于Runnable狀態(tài),它也有可能不在運行,這是因為還存在優(yōu)先級和調度問題?!馼locked(阻塞/掛起狀態(tài)):線程處于阻塞狀態(tài)。這是由兩種可能性造成的:因掛起而暫停;由于某些原因而阻塞,例如等待IO請求的完成等。●dead(終止狀態(tài)):線程轉到退出狀態(tài)。這有兩種可能性:run()方法執(zhí)行結束;調用了stop()方法。
一個Thread對象在它的生命周期中會處于各種不同的狀態(tài)。圖10.4形象地說明了這點。盡管線程變?yōu)榭蛇\行的,但它并不立即開始運行。在一個只帶有一個處理機的機器上,某一個時刻只能進行一個動作。在Java中,線程是搶占式的,但并不一定是分時的(一個常見的概念錯誤是認為“搶占式”只不過是“分時”的一種別稱而已)。搶占式調度模型是指可能有多個線程是可運行的,但只有一個線程在實際運行。
這個線程會一直運行,直至它不再是可運行的,或者另一個具有更高優(yōu)先級的線程成為可運行的。對于后面一種情形,則是因低優(yōu)先級線程被高優(yōu)先級線程搶占了運行的機會。
一個線程可能因為各種原因而不再是可運行的:線程的代碼可能執(zhí)行了一個Thread.sleep()調用,要求這個線程暫停一段固定的時間;這個線程可能在等待訪問某個資源,而且在這個資源可訪問之前,這個線程無法繼續(xù)運行。圖10.4
所有可運行線程根據優(yōu)先級保存在池中。當一個被阻塞的線程變成可運行時,它會被放回相應的可運行池。優(yōu)先級最高的非空池中的線程會得到處理機時間(被運行)。因為Java線程不一定是分時的,所有你必須確保你的代碼中的線程會不時地給另外一個線程運行的機會。這可以通過在各種時間間隔中發(fā)出sleep()調用來做到。來看如下程序段:
publicclassTestimplementsRunnable
{ publicvoidrun(){while(true){ //dolotsofinterestingstuff//Giveotherthreadsachancetry{Thread.sleep(10);}catch(InterruptedExceptione){//Thisthread'ssleepwasinterrupted//byanotherthread} } }}
注意try和catch塊的使用。Thread.sleep()和其他使線程暫停一段時間的方法是可中斷的。線程可以調用另外一個線程的interrupt()方法,這將向暫停的線程發(fā)出一個InterruptedException。
Thread類的sleep()方法對當前線程操作,因此被稱作Thread.sleep(x),它是一個靜態(tài)方法。sleep()的參數指定以毫秒為單位的線程最小休眠時間,除非線程因為中斷而提早恢復執(zhí)行,否則它不會在這段時間之前恢復執(zhí)行。Thread類的另一個方法yield(),可以用來使具有相同優(yōu)先級的線程獲得執(zhí)行的機會。如果具有相同優(yōu)先級的其他線程是可運行的,yield()將把調用線程放到可運行池中并使另一個線程運行。如果沒有相同優(yōu)先級的可運行進程,yield()什么都不做。
sleep()調用會給較低優(yōu)先級線程一個運行的機會。yield()方法只會給相同優(yōu)先級線程一個執(zhí)行的機會。10.2.2線程的調度到目前為止,我們已經學習了創(chuàng)建和管理線程的基本知識。你需要做的就是啟動一個線程,并讓它運行。你的應用程序也許希望等待一個線程執(zhí)行完畢,也許打算發(fā)送一個信息給線程,或者只打算讓線程在處理之前休眠一會兒。線程類提供了四種對線程進行操作的重要方法:sleep()、join()、wait()和notify()。sleep()方法是使線程停止一段時間的方法。在sleep時間間隔期滿后,線程不一定立即恢復執(zhí)行。這是因為在那個時刻,其他線程可能正在運行而且沒有被調度為放棄執(zhí)行,除非:
(a)“醒來”的線程具有更高的優(yōu)先級;
(b)正在運行的線程因為其他原因而阻塞。
如果一個應用程序需要執(zhí)行很多時間,比如一個耗時很長的計算工作,你可以把該計算工作設計成線程。但是,假定還有另外一個線程需要計算結果,當計算結果出來后,如何讓那個線程知道計算結果呢?解決該問題的一個方法是讓第二個線程一直不停地檢查一些變量的狀態(tài),直到這些變量的狀態(tài)發(fā)生改變。這樣的方式在Unix風格的服務器中常常用到。Java提供了一個更加簡單的機制,即線程類中的join()方法。join()方法使得一個線程等待另外一個線程結束后再執(zhí)行。例如,一個GUI(或者其他線程)使用join()方法等待一個子線程執(zhí)行完畢:
CompleteCalcThreadt=newCompleteCalcThread(); t.start(); //做一會兒其他的事情//然后等待
t.join(); //使用計算結果……join()方法有三種格式:●voidjoin():等待線程執(zhí)行完畢?!駐oidjoin(longtimeout):最多等待某段時間讓線程完成?!駐oidjoin(longmilliseconds,
intnanoseconds):最多等待某段時間(毫秒+納秒),讓線程完成。
線程APIisAlive()同join()相關聯時,是很有用的。一個線程在start(此時run()方法已經啟動)之后,在stop之前的某時刻處于isAlive狀態(tài)。對于編寫線程的程序員來說,還有其他兩個有用的方法,即wait()和notify()。使用這兩個API,我們可以精確地控制線程的執(zhí)行過程。關于這兩個方法的使用,將在后面詳細解釋。10.2.3線程的優(yōu)先級線程可以設定優(yōu)先級,高優(yōu)先級的線程可以安排在低優(yōu)先級線程之前完成。一個應用程序可以通過使用線程中的setPriority(int)方法來設置線程的優(yōu)先級大小。對于多線程程序,每個線程的重要程度是不盡相同的,如多個線程在等待獲得CPU時間時,往往需要優(yōu)先級高的線程優(yōu)先搶占到CPU時間得以執(zhí)行;又如多個線程交替執(zhí)行時,優(yōu)先級決定了級別高的線程得到CPU的次數多一些且時間長一些。這樣,高優(yōu)先級的線程處理的任務效率就高一些。Java中,線程的優(yōu)先級從低到高以整數1~10表示,共分為10級。設置優(yōu)先級是通過調用線程對象的setPriority()方法來進行的。設置優(yōu)先級的語句為
Threadthreadone=newThread();
//用Thread類的子類創(chuàng)建線程
Threadthreadtwo=newThread();
threadone.setPriority(6);//設置threadone的優(yōu)先級為6
threadtwo.setPriority(3);//設置threadtwo的優(yōu)先級為3
threadone.start();threadtwo.start();//strat()方法啟動線程這樣,線程threadone將會優(yōu)先于線程threadtwo執(zhí)行,并將占有更多的CPU時間。該例中,優(yōu)先級設置放在線程啟動前。也可以在啟動后進行優(yōu)先級設置,以滿足不同的優(yōu)先級需求。10.3線程組
通常,一個程序可能包含若干線程,如何來管理這些線程呢?把這些線程按功能分類是個不錯的辦法。Java語言提供了線程組,線程組可以讓你同時控制一組線程。實際上,線程組就是一種可以管理一組線程的類。
可以用構造方法ThreadGroup()來構造一個線程組,如下所示:
StringgrounName=…; ThreadGroupg=newThreadGroup(groupName);
ThreadGroup()方法的參數表示一個線程組,因此該串參數必須是惟一的。也可以用Thread類的構造方法往一個指定的線程組里添加新的線程:
Threadt=newThread(g,threadName);
activeCount()方法用于檢測某個指定線程組是否有線程處于活動狀態(tài):
if(g.activeCount()==0) {//線程g的所有線程都已停止}
要中斷一個線程組中的所有線程,可以調用ThreadGroup類的方法interrupt():
errupt();
線程組可以嵌套,即線程組可以擁有子線程組。缺省時,一個新創(chuàng)建的線程或線程組都屬于當前線程組所屬的線程組。線程組的常用方法如下:●ThreadGroup(Stringname):創(chuàng)建一個新線程組,它的父線程組是當前線程組?!馮hreadGroup(ThreadGroupparent,Stringname):創(chuàng)建一個新線程組,其父線程組由parent參數指定。●intactiveCount():返回當前線程組活動線程的上限?!駃ntenumerate(Thread[]list):得到當前線程組各個活動線程的地址?!馮hreadGroupgetParent():得到當前線程組的父線程組?!馰oidinterrupt():中斷線程組中所有線程及其子線程組中所有線程。10.4多線程程序的開發(fā)10.4.1synchronized的基本概念關鍵字synchronized提供Java編程語言一種機制,允許程序員控制共享數據的線程。本節(jié)重點討論其使用方法。我們已經知道,進程允許兩個或者更多個線程同時執(zhí)行。實際上,這些線程也可以共享對象和數據,但在這種情形下,不同的線程在同一時間內不能存取同一數據,這是因為在開始設計Java的時候,就采用了線程的概念。Java語言定義了一個特殊的關鍵字synchronized(同步),該關鍵字可以應用到代碼塊上(代碼塊也包括入口方法)。該關鍵字的目的是防止多個線程在同一時間執(zhí)行同一代碼塊內的代碼。定義一個同步方法的格式如下:
[public|private]synchronized{type}
methodname(...)
一個簡單的應用例子如下:
publicclasssomeClass
{ publicvoidaMethod(){ ... synchronized(this){ //Synchronizedcodeblock}...}}
同步化的關鍵字可以保證在同一時間內只有一個線程可以執(zhí)行某代碼段,而任何其他要用到該段代碼的線程將被阻塞,直到第一個線程執(zhí)行完該段代碼,如圖10.5所示對象鎖標志synchronized到底是如何做到保證資源訪問同步的呢?在Java技術中,每個對象都有一個和它相關聯的標志。這個標志可以被認為是“鎖標志”。synchronized關鍵字能保證多線程之間的同步運行,即允許獨占地存取對象。當線程運行到synchronized語句時,它檢查作為參數傳遞的對象,并在繼續(xù)執(zhí)行之前試圖從對象獲得鎖標志。圖10.5
意識到它自身并沒有保護數據是很重要的。因為如果同一個對象的pop()方法沒有受到synchronized的影響,且pop()是由另一個線程調用的,那么仍然存在破壞data的一致性的危險。如果要使鎖有效,所有存取共享數據的方法必須在同一把鎖上同步。圖10.6顯示了如果pop()受到synchronized的影響,且另一個線程在原線程持有那個對象的鎖時試圖執(zhí)行pop()方法時所發(fā)生的事情:圖10.6
當線程試圖執(zhí)行synchronized(this)語句時,它試圖從this對象獲取鎖標志。由于得不到標志,所以線程不能繼續(xù)運行。然后,線程加入到與那個對象鎖相關聯的等待線程池中。當標志返回給對象時,某個等待這個標志的線程將得到這把鎖并繼續(xù)運行。由于等待一個對象的鎖標志的線程在得到標志之前不能恢復運行,所以讓持有鎖標志的線程在不再需要的時候返回標志是很重要的。
鎖標志將自動返回給它的對象。持有鎖標志的線程執(zhí)行到synchronized()代碼塊末尾時將釋放鎖。Java技術特別注意了保證即使出現中斷或異常而使得執(zhí)行流跳出synchronized()代碼塊,鎖也會自動返回。此外,如果一個線程對同一個對象兩次發(fā)出synchronized調用,則在跳出最外層的塊時,標志會正確地釋放,而最內層的將被忽略。這些規(guī)則使得與其他系統(tǒng)中的等價功能相比,管理同步塊的使用簡單了很多。10.4.2多線程的控制線程有兩個缺陷:死鎖和饑餓。所謂死鎖,就是一個或者多個線程,在一個給定的任務中,協(xié)同作用,互相干涉,從而導致一個或者更多線程永遠等待下去。與此類似,所謂饑餓,就是一個線程永久性地占有資源,使得其他線程得不到該資源。
首先我們看一下死鎖的問題。一個簡單的例子就是:你到ATM機上取錢,卻看到如下的信息“現在沒有現金,請等會兒再試?!?,你需要錢,所以你就等了一會兒再試,但是你又看到了同樣的信息;與此同時,在你后面,一輛運款車正等待著把錢放進ATM機中,但是運款車到不了ATM取款機,因為你的汽車擋著道。在這種情況下,就發(fā)生了所謂的死鎖。
在饑餓的情形下,系統(tǒng)并不處于死鎖狀態(tài)中,因為有一個進程仍在處理之中,只是其他進程永遠得不到執(zhí)行的機會而已。在什么樣的環(huán)境下,會導致饑餓的發(fā)生,并沒有預先確定好的規(guī)則。但一旦發(fā)生下面四種情況之一,就會導致死鎖的發(fā)生?!裣嗷ヅ懦猓阂粋€線程或者進程永遠占有一共享資源,例如,獨占該資源?!裱h(huán)等待:進程A等待進程B,而后者又在等待進程C,而進程C又在等待進程A?!癫糠址峙洌嘿Y源被部分分配。例如,進程A和B都需要訪問一個文件,并且都要用到打印機,進程A獲得了文件資源,進程B獲得了打印機資源?!袢鄙賰?yōu)先權:一個進程訪問了某個資源,但是一直不釋放該資源,即使該進程處于阻塞狀態(tài)。為了避免出現死鎖的情況,我們就必須在多線程程序中做同步管理,為此必須編寫使它們交互的程序。java.lang.Object類中提供了兩個用于線程通信的方法:wait()和notify()。如果線程對一個同步對象x發(fā)出一個wait()調用,則該線程會暫停執(zhí)行,直到另一個線程對同一個同步對象x也發(fā)出一個wait()調用。為了讓線程對一個對象調用wait()或notify(),線程必須鎖定那個特定的對象。也就是說,只能在它們被調用的實例的同步塊內使用wait()和notify()。當某個線程執(zhí)行包含對一個特定對象執(zhí)行wait()調用的同步代碼時,這個線程就被放到與那個對象相關的等待池中。此外,調用wait()的線程自動釋放對象的鎖標志。
對一個特定對象執(zhí)行notify()調用時,將從對象的等待池中移走一個任意的線程,并放到鎖池中。鎖池中的對象一直在等待,直到可以獲得對象的鎖標記。notifyAll()方法將從等待池中移走所有等待那個對象的線程,并把它們放到鎖池中。只有鎖池中的線程能獲取對象的鎖標記,鎖標記允許線程從上次因調用wait()而中斷的地方開始繼續(xù)運行。
在許多實現了wait()/notify()機制的系統(tǒng)中,醒來的線程必定是那個等待時間最長的線程。然而,在Java技術中,并不能保證這點。應注意的是,不管是否有線程在等待,用戶都可以調用notify()。如果對一個對象調用notify()方法,而在這個對象的鎖標記等待池中并沒有阻塞的線程,那么調用notify()將不起任何作用。對notify()的調用不會被存儲。
下面給出一個線程交互的實例,用于說明如何使用synchronized關鍵字和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;}}
本例實現了兩個線程,每個線程輸出1~100的數字。工作程序是:第一個線程輸出1~10,停止,通知第二個線程;第二個線程輸出1~10,停止,再通知第一個線程;第一個線程輸出11~20……
在Java中,每個對象都有個對象鎖標志(Objectlockflag)與之想關聯,當一個線程A調用對象的一段synchronized代碼時,它首先要獲取與這個對象關聯的對象鎖標志,然后才執(zhí)行相應的代碼,執(zhí)行結束后,把這個對象鎖標志返回給對象。因此,在線程A執(zhí)行synchronized代碼期間,如果另一個線程B也要執(zhí)行同一對象的一段synchronized代碼(不一定與線程A執(zhí)行的相同),那么它必須等到線程A執(zhí)行完后,才能繼續(xù)。
在synchronized代碼被執(zhí)行期間,線程可以調用對象的wait()方法,釋放對象鎖標志,進入等待狀態(tài),并且可以調用notify()或者notifyAll()方法通知正在等待的其他線程。notify()方法通知等待隊列中的第一個線程,notifyAll()方法通知的是等待隊列中的所有線程。程序的部分輸出結果如下:
@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********************************
線程中還有三個控制線程執(zhí)行的方法:suspend()、resume()和stop()方法。但從JDK1.2開始,Java標準不贊成使用它們來控制線程的執(zhí)行,而是用wait()和notify()來代替它們。這里我們只對這三個方法做個簡單地介紹。●suspend()和resume()方法
resume()方法的惟一作用就是恢復被掛起的線程。所以,如果沒有suspend(),resume()也就沒有存在的必要。從設計的角度來看,有兩個原因使得使用suspend()非常危險:它容易產生死鎖;它允許一個線程控制另一個線程代碼的執(zhí)行。下面分別介紹這兩種危險。假設有兩個線程:threadA和threadB。當正在執(zhí)行它們的代碼時,threadB獲得一個對象的鎖,然后繼續(xù)它的任務。當threadA的執(zhí)行代碼調用threadB.suspend()時,將使threadB停止執(zhí)行它的代碼。
如果threadB.suspend()沒有使threadB釋放它所持有的鎖,就會發(fā)生死鎖。如果調用threadB.resume()的線程需要threadB仍持有的鎖,這兩個線程就會陷入死鎖。假設threadA調用threadB.suspend()。如果threadB被掛起時threadA獲得控制,那么threadB就永遠得不到機會來進行清除工作,例如使它正在操作的共享數據處于穩(wěn)定狀態(tài)。為了安全起見,只有threadB才可以決定何時停止它自己的代碼。
你應該使用對同步對象調用wait()和notify()的機制來代替suspend()和resume()進行線程控制。這種方法是通過執(zhí)行wait()調用來強制線程決定何時“掛起”自己。這使得同步對象的鎖被自動釋放,并給予線程一個在調用wait()之前穩(wěn)定任何數據的機會?!駍top()方法
stop()方法的情形是類似的,但結果有所不同。如果一個線程在持有一個對象鎖的時候被停止,它將在終止之前釋放它持有的鎖。這避免了前面所討論的死鎖問題,但它又引入了其他問題。
在前面的范例中,如果線程在已將字符加入棧但還沒有使下標值加1之后被停止,則在釋放鎖的時候會得到一個不一致的棧結構??倳幸恍╆P鍵操作需要不可分割地執(zhí)行,而且在線程執(zhí)行這些操作時被停止就會破壞操作的不可分割性。
一個關于停止線程的獨立而又重要的問題涉及線程的總體設計策略。創(chuàng)建線程來執(zhí)行某個特定作業(yè),并存活于整個程序的生命周期。換言之,你不會這樣來設計程序:隨意地創(chuàng)建和處理線程,或創(chuàng)建無數個對話框或socket端點。每個線程都會消耗系統(tǒng)資源,而系統(tǒng)資源并不是無限的。這并不是暗示一個線程必須連續(xù)執(zhí)行;它只是簡單地意味著應當使用合適而安全的wait()和notify()機制來控制線程。10.4.3多線程之間的通信
Java語言提供了各種各樣的輸入/輸出流,使我們能夠很方便地對數據進行操作。其中,管道(Pipe)流是一種特殊的流,用于在不同線程間直接傳送數據(一個線程發(fā)送數據到輸出管道,另一個線程從輸入管道中讀數據)。通過使用管道,就可實現不同線程間的通信,無需求助于類似臨時文件之類的東西。Java提供了兩個特殊的、專門的類用于處理管道,它們就是PipedInputStream類和PipedOutputStream類。PipedInputStream代表了數據在管道中的輸出端,也就是線程向管道讀數據的一端;PipedOutputStream代表了數據在管道中的輸入端,也就是線程向管道寫數據的一端,這兩個類一起使用可以提供數據的管道流。為了創(chuàng)建一個管道流,我們必須首先創(chuàng)建一個PipedOutStream對象,然后創(chuàng)建PipedInputStream對象,前面我們已經介紹過。
一旦創(chuàng)建了一個管道,就可以像操作文件一樣對管道進行數據的讀/寫。下面我們來看一個運用管道實現線程間通信的實例。例10.5PipeTest.javaimportjava.util.*;importjava.io.*;
publicclassPipeTest
{
publicstaticvoidmain(Stringargs[]){
try{ /*setuppipes*/PipedOutputStreampout1=newPipedOutputStream();PipedInputStreampin1=newPipedInputStream(pout1)
PipedOutputStreampout2=newPipedOutputStream();Piped
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 科技助力學校安全教育培訓的實踐與探索
- 營養(yǎng)、快捷兩不誤-高效選配工作餐之秘訣探索
- 2025年度藝術品展覽與技術支持服務合同
- 腎臟疾病診斷的突破尿檢技術的創(chuàng)新與應用案例分析
- 2025年度瓷磚美縫施工與驗收標準合同
- 2025年度石油化工短期勞務合同
- 二零二五年度醫(yī)療健康合資經營合同模板
- 2025年度雕塑設計專利申請與授權合同
- 2025年度酒店停車場管理與收費服務合同
- 二零二五年度影視后期制作團隊雇傭合同與勞務合同
- 湖南省長沙市長郡教育集團2024-2025學年七年級上學期期末考試英語試題(含答案)
- 2024-2025學年人教版三年級(上)英語寒假作業(yè)(九)
- 立春氣象與健康
- 河南退役軍人專升本計算機真題答案
- 室內空氣治理技術培訓
- 2024-2025學年上外版高二上學期期中英語試卷與參考答案
- DB52T 1167-2017 含笑屬栽培技術規(guī)程 樂昌含笑
- 2025年全國高考體育單招考試政治模擬試卷試題(含答案詳解)
- 駕駛證學法減分(學法免分)試題和答案(50題完整版)1650
- 人教版2024新版七年級上冊數學第六章幾何圖形初步學業(yè)質量測試卷(含答案)
- 小學數學五年級上冊奧數應用題100道(含答案)
評論
0/150
提交評論