




已閱讀5頁,還剩13頁未讀, 繼續(xù)免費(fèi)閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
Java多線程編程總結(jié)Java語言的一個(gè)重要特點(diǎn)是內(nèi)在支持多線程的程序設(shè)計(jì)。多線程是指在單個(gè)的程序內(nèi)可以同時(shí)運(yùn)行多個(gè)不同的線程完成不同的任務(wù)。多線程的程序設(shè)計(jì)具有廣泛的應(yīng)用。本章主要講授線程的概念、如何創(chuàng)建多線程的程序、線程的生存周期與狀態(tài)的改變、線程的同步與互斥等內(nèi)容。9.1 線程與線程類9.1.1 線程的概念線程的概念來源于計(jì)算機(jī)的操作系統(tǒng)的進(jìn)程的概念。進(jìn)程是一個(gè)程序關(guān)于某個(gè)數(shù)據(jù)集的一次運(yùn)行。也就是說,進(jìn)程是運(yùn)行中的程序,是程序的一次運(yùn)行活動。線程和進(jìn)程的相似之處在于,線程和運(yùn)行的程序都是單個(gè)順序控制流。有些教材將線程稱為輕量級進(jìn)程(light weight process)。線程被看作是輕量級進(jìn)程是因?yàn)樗\(yùn)行在一個(gè)程序的上下文內(nèi),并利用分配給程序的資源和環(huán)境。作為單個(gè)順序控制流,線程必須在運(yùn)行的程序中得到自己運(yùn)行的資源,如必須有自己的執(zhí)行棧和程序計(jì)數(shù)器。線程內(nèi)運(yùn)行的代碼只能在該上下文內(nèi)。因此還有些教程將執(zhí)行上下文(execution context)作為線程的同義詞。所有的程序員都熟悉順序程序的編寫,如我們編寫的名稱排序和求素?cái)?shù)的程序就是順序程序。順序程序都有開始、執(zhí)行序列和結(jié)束,在程序執(zhí)行的任何時(shí)刻,只有一個(gè)執(zhí)行點(diǎn)。線程(thread)則是進(jìn)程中的一個(gè)單個(gè)的順序控制流。單線程的概念很簡單,如圖9.1所示。多線程(multi-thread)是指在單個(gè)的程序內(nèi)可以同時(shí)運(yùn)行多個(gè)不同的線程完成不同的任務(wù),圖9.2說明了一個(gè)程序中同時(shí)有兩個(gè)線程運(yùn)行。一個(gè)線程兩個(gè)線程圖9.1 單線程程序示意圖 圖9.2 多線程程序示意圖有些程序中需要多個(gè)控制流并行執(zhí)行。例如,for(int i = 0; i 100; i+) System.out.println(Runner A = + i);for(int j = 0; j 100; j+ ) System.out.println(Runner B = +j);上面的代碼段中,在只支持單線程的語言中,前一個(gè)循環(huán)不執(zhí)行完不可能執(zhí)行第二個(gè)循環(huán)。要使兩個(gè)循環(huán)同時(shí)執(zhí)行,需要編寫多線程的程序。很多應(yīng)用程序是用多線程實(shí)現(xiàn)的,如Hot Java Web瀏覽器就是多線程應(yīng)用的例子。在Hot Java 瀏覽器中,你可以一邊滾動屏幕,一邊下載Applet或圖像,可以同時(shí)播放動畫和聲音等。9.1.2 Thread類和Runnable接口多線程是一個(gè)程序中可以有多段代碼同時(shí)運(yùn)行,那么這些代碼寫在哪里,如何創(chuàng)建線程對象呢?首先,我們來看Java語言實(shí)現(xiàn)多線程編程的類和接口。在java.lang包中定義了Runnable接口和Thread類。Runnable接口中只定義了一個(gè)方法,它的格式為: public abstract void run() 這個(gè)方法要由實(shí)現(xiàn)了Runnable接口的類實(shí)現(xiàn)。Runnable對象稱為可運(yùn)行對象,一個(gè)線程的運(yùn)行就是執(zhí)行該對象的run()方法。Thread類實(shí)現(xiàn)了Runnable接口,因此Thread對象也是可運(yùn)行對象。同時(shí)Thread類也是線程類,該類的構(gòu)造方法如下: public Thread() public Thread(Runnable target) public Thread(String name) public Thread(Runnable target, String name) public Thread(ThreadGroup group, Runnable target) public Thread(ThreadGroup group, String name) public Thread(ThreadGroup group, Runnable target, String name)target為線程運(yùn)行的目標(biāo)對象,即線程調(diào)用start()方法啟動后運(yùn)行那個(gè)對象的run()方法,該對象的類型為Runnable,若沒有指定目標(biāo)對象,則以當(dāng)前類對象為目標(biāo)對象;name為線程名,group指定線程屬于哪個(gè)線程組(有關(guān)線程組的概念請參考9.6節(jié))。Thread類的常用方法有: public static Thread currentThread() 返回當(dāng)前正在執(zhí)行的線程對象的引用。 public void setName(String name) 設(shè)置線程名。 public String getName() 返回線程名。 public static void sleep(long millis) throws InterruptedException public static void sleep(long millis, int nanos) throws InterruptedException使當(dāng)前正在執(zhí)行的線程暫時(shí)停止執(zhí)行指定的毫秒時(shí)間。指定時(shí)間過后,線程繼續(xù)執(zhí)行。該方法拋出InterruptedException異常,必須捕獲。 public void run() 線程的線程體。 public void start() 由JVM調(diào)用線程的run()方法,啟動線程開始執(zhí)行。 public void setDaemon(boolean on) 設(shè)置線程為Daemon線程。 public boolean isDaemon() 返回線程是否為Daemon線程。 public static void yield() 使當(dāng)前執(zhí)行的線程暫停執(zhí)行,允許其他線程執(zhí)行。 public ThreadGroup getThreadGroup() 返回該線程所屬的線程組對象。 public void interrupt() 中斷當(dāng)前線程。 public boolean isAlive() 返回指定線程是否處于活動狀態(tài)。9.2 線程的創(chuàng)建本節(jié)介紹如何創(chuàng)建和運(yùn)行線程的兩種方法。線程運(yùn)行的代碼就是實(shí)現(xiàn)了Runnable接口的類的run()方法或者是Thread類的子類的run()方法,因此構(gòu)造線程體就有兩種方法: 繼承Thread類并覆蓋它的run()方法; 實(shí)現(xiàn)Runnable接口并實(shí)現(xiàn)它的run()方法。9.2.1 繼承Thread類創(chuàng)建線程通過繼承Thread類,并覆蓋run()方法,這時(shí)就可以用該類的實(shí)例作為線程的目標(biāo)對象。下面的程序定義了SimpleThread類,它繼承了Thread類并覆蓋了run()方法。程序9.1 SimpleThread.javapublic class SimpleThread extends Thread public SimpleThread(String str) super(str);public void run() for(int i=0; i100; i+) System.out.println(getName()+ = + i); try sleep(int)(Math.random()*100); catch(InterruptedException e) System.out.println(getName()+ DONE);_ SimpleThread類繼承了Thread類,并覆蓋了run()方法,該方法就是線程體。程序9.2 ThreadTest.javapublic class ThreadTest public static void main(String args) Thread t1 = new SimpleThread(Runner A); Thread t2 = new SimpleThread(Runner B); t1.start(); t2.start(); _在ThreadTest類的main()方法中創(chuàng)建了兩個(gè)SimpleThread類的線程對象并調(diào)用線程類的start()方法啟動線程。構(gòu)造線程時(shí)沒有指定目標(biāo)對象,所以線程啟動后執(zhí)行本類的run()方法。注意,實(shí)際上ThreadTest程序中有三個(gè)線程同時(shí)運(yùn)行。請?jiān)囍鴮⑾露未a加到main()方法中,分析程序運(yùn)行結(jié)果。for(int i=0; i100; i+) System.out.println(Thread.currentThread().getName()+=+ i); try Thread.sleep(int)(Math.random()*500); catch(InterruptedException e) System.out.println(Thread.currentThread().getName()+ DONE); 從上述代碼執(zhí)行結(jié)果可以看到,在應(yīng)用程序的main()方法啟動時(shí),JVM就創(chuàng)建一個(gè)主線程,在主線程中可以創(chuàng)建其他線程。再看下面的程序:程序9.3 MainThreadDemo.javapublic class MainThreadDemo public static void main(String args) Thread t = Thread.currentThread(); t.setName(MyThread); System.out.println(t); System.out.println(t.getName(); System.out.println(t.getThreadGroup().getName(); _該程序輸出結(jié)果為:ThreadMyThread, 5, mainMyThreadmain上述程序在main()方法中聲明了一個(gè)Thread對象t,然后調(diào)用Thread類的靜態(tài)方法currentThread()獲得當(dāng)前線程對象。然后重新設(shè)置該線程對象的名稱,最后輸出線程對象、線程組對象名和線程對象名。9.2.2 實(shí)現(xiàn)Runnable接口創(chuàng)建線程可以定義一個(gè)類實(shí)現(xiàn)Runnable接口,然后將該類對象作為線程的目標(biāo)對象。實(shí)現(xiàn)Runnable接口就是實(shí)現(xiàn)run()方法。下面程序通過實(shí)現(xiàn)Runnable接口構(gòu)造線程體。程序9.4 ThreadTest.javaclass T1 implements Runnable public void run() for(int i=0;i15;i+) System.out.println(Runner A=+i); class T2 implements Runnable public void run() for(int j=0;j15;j+) System.out.println(Runner B=+j); public class ThreadTest public static void main(String args) Thread t1=new Thread(new T1(),Thread A); Thread t2=new Thread(new T2(),Thread B); t1.start(); t2.start(); _下面是一個(gè)小應(yīng)用程序,利用線程對象在其中顯示當(dāng)前時(shí)間。程序9.5 ThreadTest.java/import java.awt.*;import java.util.*;import javax.swing.*;import java.text.DateFormat;public class ClockDemo extends JApplet private Thread clockThread = null; private ClockPanel cp=new ClockPanel(); public void init() getContentPane().add(cp); public void start() if (clockThread = null) clockThread = new Thread(cp, Clock); clockThread.start(); public void stop() clockThread = null; class ClockPanel extends JPanel implements Runnable public void paintComponent(Graphics g) super.paintComponent(g); Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); DateFormat dateFormatter = DateFormat.getTimeInstance(); g.setColor(Color.BLUE); g.setFont(new Font(TimesNewRoman,Font.BOLD,36); g.drawString(dateFormatter.format(date), 50, 50); public void run() while (true) repaint(); try Thread.sleep(1000); catch (InterruptedException e) _該小應(yīng)用程序的運(yùn)行結(jié)果如圖9.3所示:圖9.3 ClockDemo的運(yùn)行結(jié)果9.3 線程的狀態(tài)與調(diào)度9.3.1 線程的生命周期線程從創(chuàng)建、運(yùn)行到結(jié)束總是處于下面五個(gè)狀態(tài)之一:新建狀態(tài)、就緒狀態(tài)、運(yùn)行狀態(tài)、阻塞狀態(tài)及死亡狀態(tài)。線程的狀態(tài)如圖9.4所示:新建狀態(tài)就緒狀態(tài)阻塞狀態(tài)運(yùn)行狀態(tài)死亡狀態(tài)圖9.4 線程的五種狀態(tài)下面以前面的Java小程序?yàn)槔f明線程的狀態(tài):1. 新建狀態(tài)(New Thread)當(dāng)Applet啟動時(shí)調(diào)用Applet的start()方法,此時(shí)小應(yīng)用程序就創(chuàng)建一個(gè)Thread對象clockThread。 public void start() if (clockThread = null) clockThread = new Thread(cp, Clock); clockThread.start(); 當(dāng)該語句執(zhí)行后clockThread就處于新建狀態(tài)。處于該狀態(tài)的線程僅僅是空的線程對象,并沒有為其分配系統(tǒng)資源。當(dāng)線程處于該狀態(tài),你僅能啟動線程,調(diào)用任何其他方法是無意義的且會引發(fā)IllegalThreadStateException異常(實(shí)際上,當(dāng)調(diào)用線程的狀態(tài)所不允許的任何方法時(shí),運(yùn)行時(shí)系統(tǒng)都會引發(fā)IllegalThreadStateException異常)。 注意cp作為線程構(gòu)造方法的第一個(gè)參數(shù),該參數(shù)必須是實(shí)現(xiàn)了Runnable接口的對象并提供線程運(yùn)行的run()方法,第二個(gè)參數(shù)是線程名。2. 就緒狀態(tài)(Runnable)一個(gè)新創(chuàng)建的線程并不自動開始運(yùn)行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當(dāng)線程對象調(diào)用start()方法即啟動了線程,如clockThread.start(); 語句就是啟動clockThread線程。start()方法創(chuàng)建線程運(yùn)行的系統(tǒng)資源,并調(diào)度線程運(yùn)行run()方法。當(dāng)start()方法返回后,線程就處于就緒狀態(tài)。處于就緒狀態(tài)的線程并不一定立即運(yùn)行run()方法,線程還必須同其他線程競爭CPU時(shí)間,只有獲得CPU時(shí)間才可以運(yùn)行線程。因?yàn)樵趩蜟PU的計(jì)算機(jī)系統(tǒng)中,不可能同時(shí)運(yùn)行多個(gè)線程,一個(gè)時(shí)刻僅有一個(gè)線程處于運(yùn)行狀態(tài)。因此此時(shí)可能有多個(gè)線程處于就緒狀態(tài)。對多個(gè)處于就緒狀態(tài)的線程是由Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。3. 運(yùn)行狀態(tài)(Running) 當(dāng)線程獲得CPU時(shí)間后,它才進(jìn)入運(yùn)行狀態(tài),真正開始執(zhí)行run()方法,這里run()方法中是一個(gè)循環(huán),循環(huán)條件是true。 public void run() while (true) repaint(); try Thread.sleep(1000); catch (InterruptedException e) 4. 阻塞狀態(tài)(Blocked)線程運(yùn)行過程中,可能由于各種原因進(jìn)入阻塞狀態(tài)。所謂阻塞狀態(tài)是正在運(yùn)行的線程沒有運(yùn)行結(jié)束,暫時(shí)讓出CPU,這時(shí)其他處于就緒狀態(tài)的線程就可以獲得CPU時(shí)間,進(jìn)入運(yùn)行狀態(tài)。有關(guān)阻塞狀態(tài)在后面詳細(xì)討論。5. 死亡狀態(tài)(Dead)線程的正常結(jié)束,即run()方法返回,線程運(yùn)行就結(jié)束了,此時(shí)線程就處于死亡狀態(tài)。本例子中,線程運(yùn)行結(jié)束的條件是clockThread為null,而在小應(yīng)用程序的stop()方法中,將clockThread賦值為null。即當(dāng)用戶離開含有該小應(yīng)用程序的頁面時(shí),瀏覽器調(diào)用stop()方法,將clockThread賦值為null,這樣在run()的while循環(huán)時(shí)條件就為false,這樣線程運(yùn)行就結(jié)束了。如果再重新訪問該頁面,小應(yīng)用程序的start()方法又會重新被調(diào)用,重新創(chuàng)建并啟動一個(gè)新的線程。 public void stop() clockThread = null; 程序不能像終止小應(yīng)用程序那樣通過調(diào)用一個(gè)方法來結(jié)束線程(小應(yīng)用程序通過調(diào)用stop()方法結(jié)束小應(yīng)用程序的運(yùn)行)。線程必須通過run()方法的自然結(jié)束而結(jié)束。通常在run()方法中是一個(gè)循環(huán),要么是循環(huán)結(jié)束,要么是循環(huán)的條件不滿足,這兩種情況都可以使線程正常結(jié)束,進(jìn)入死亡狀態(tài)。例如,下面一段代碼是一個(gè)循環(huán):public void run() int i = 0; while(i100) i+; System.out.println(i = + i );當(dāng)該段代碼循環(huán)結(jié)束后,線程就自然結(jié)束了。注意一個(gè)處于死亡狀態(tài)的線程不能再調(diào)用該線程的任何方法。9.3.2 線程的優(yōu)先級和調(diào)度Java的每個(gè)線程都有一個(gè)優(yōu)先級,當(dāng)有多個(gè)線程處于就緒狀態(tài)時(shí),線程調(diào)度程序根據(jù)線程的優(yōu)先級調(diào)度線程運(yùn)行??梢杂孟旅娣椒ㄔO(shè)置和返回線程的優(yōu)先級。 public final void setPriority(int newPriority) 設(shè)置線程的優(yōu)先級。 public final int getPriority() 返回線程的優(yōu)先級。newPriority為線程的優(yōu)先級,其取值為1到10之間的整數(shù),也可以使用Thread類定義的常量來設(shè)置線程的優(yōu)先級,這些常量分別為:Thread.MIN_PRIORITY、Thread.NORM_PRIORITY、Thread.MAX_PRIORITY,它們分別對應(yīng)于線程優(yōu)先級的1、5和10,數(shù)值越大優(yōu)先級越高。當(dāng)創(chuàng)建Java線程時(shí),如果沒有指定它的優(yōu)先級,則它從創(chuàng)建該線程那里繼承優(yōu)先級。一般來說,只有在當(dāng)前線程停止或由于某種原因被阻塞,較低優(yōu)先級的線程才有機(jī)會運(yùn)行。前面說過多個(gè)線程可并發(fā)運(yùn)行,然而實(shí)際上并不總是這樣。由于很多計(jì)算機(jī)都是單CPU的,所以一個(gè)時(shí)刻只能有一個(gè)線程運(yùn)行,多個(gè)線程的并發(fā)運(yùn)行只是幻覺。在單CPU機(jī)器上多個(gè)線程的執(zhí)行是按照某種順序執(zhí)行的,這稱為線程的調(diào)度(scheduling)。大多數(shù)計(jì)算機(jī)僅有一個(gè)CPU,所以線程必須與其他線程共享CPU。多個(gè)線程在單個(gè)CPU是按照某種順序執(zhí)行的。實(shí)際的調(diào)度策略隨系統(tǒng)的不同而不同,通常線程調(diào)度可以采用兩種策略調(diào)度處于就緒狀態(tài)的線程。(1) 搶占式調(diào)度策略 Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度算法是搶占式的 (preemptive)。Java運(yùn)行時(shí)系統(tǒng)支持一種簡單的固定優(yōu)先級的調(diào)度算法。如果一個(gè)優(yōu)先級比其他任何處于可運(yùn)行狀態(tài)的線程都高的線程進(jìn)入就緒狀態(tài),那么運(yùn)行時(shí)系統(tǒng)就會選擇該線程運(yùn)行。新的優(yōu)先級較高的線程搶占(preempt)了其他線程。但是Java運(yùn)行時(shí)系統(tǒng)并不搶占同優(yōu)先級的線程。換句話說,Java運(yùn)行時(shí)系統(tǒng)不是分時(shí)的(time-slice)。然而,基于Java Thread類的實(shí)現(xiàn)系統(tǒng)可能是支持分時(shí)的,因此編寫代碼時(shí)不要依賴分時(shí)。當(dāng)系統(tǒng)中的處于就緒狀態(tài)的線程都具有相同優(yōu)先級時(shí),線程調(diào)度程序采用一種簡單的、非搶占式的輪轉(zhuǎn)的調(diào)度順序。(2) 時(shí)間片輪轉(zhuǎn)調(diào)度策略有些系統(tǒng)的線程調(diào)度采用時(shí)間片輪轉(zhuǎn)(round-robin)調(diào)度策略。這種調(diào)度策略是從所有處于就緒狀態(tài)的線程中選擇優(yōu)先級最高的線程分配一定的CPU時(shí)間運(yùn)行。該時(shí)間過后再選擇其他線程運(yùn)行。只有當(dāng)線程運(yùn)行結(jié)束、放棄(yield)CPU或由于某種原因進(jìn)入阻塞狀態(tài),低優(yōu)先級的線程才有機(jī)會執(zhí)行。如果有兩個(gè)優(yōu)先級相同的線程都在等待CPU,則調(diào)度程序以輪轉(zhuǎn)的方式選擇運(yùn)行的線程。9.4 線程狀態(tài)的改變新建狀態(tài)一個(gè)線程在其生命周期中可以從一種狀態(tài)改變到另一種狀態(tài),線程狀態(tài)的變遷如圖9.5所示:yield()run()schedulerstart()死亡狀態(tài)運(yùn)行狀態(tài)就緒狀態(tài)阻塞狀態(tài)sleep()I/O操作join()wait()圖9.5 線程狀態(tài)的改變9.4.1 控制線程的啟動和結(jié)束當(dāng)一個(gè)新建的線程調(diào)用它的start()方法后即進(jìn)入就緒狀態(tài),處于就緒狀態(tài)的線程被線程調(diào)度程序選中就可以獲得CPU時(shí)間,進(jìn)入運(yùn)行狀態(tài),該線程就開始運(yùn)行run()方法。控制線程的結(jié)束稍微復(fù)雜一點(diǎn)。如果線程的run()方法是一個(gè)確定次數(shù)的循環(huán),則循環(huán)結(jié)束后,線程運(yùn)行就結(jié)束了,線程對象即進(jìn)入死亡狀態(tài)。如果run()方法是一個(gè)不確定循環(huán),早期的方法是調(diào)用線程對象的stop()方法,然而由于該方法可能導(dǎo)致線程死鎖,因此從1.1版開始,不推薦使用該方法結(jié)束線程。一般是通過設(shè)置一個(gè)標(biāo)志變量,在程序中改變標(biāo)志變量的值實(shí)現(xiàn)結(jié)束線程。請看下面的例子:程序9.6 ThreadStop.javaimport java.util.*;class Timer implements Runnableboolean flag=true;public void run() while(flag) System.out.print(rt+new Date()+.); try Thread.sleep(1000); catch(InterruptedException e) System.out.println(n+Thread.currentThread().getName()+ Stop);public void stopRun()flag = false;public class ThreadStoppublic static void main(String args) Timer timer = new Timer(); Thread thread = new Thread(timer); thread.setName(Timer); thread.start(); for(int i=0;i100;i+) System.out.print(r+i); try Thread.sleep(100); catch(InterruptedException e) timer.stopRun();_該程序在Timer類中定義了一個(gè)布而變量flag,同時(shí)定義了一個(gè)stopRun()方法,在其中將該變量設(shè)置為false。在主程序中通過調(diào)用該方法,從而改變該變量的值,使得run()方法的while循環(huán)條件不滿足,從而實(shí)現(xiàn)結(jié)束線程的運(yùn)行。說明 在Thread類中除了stop()方法被標(biāo)注為不推薦(deprecated) 使用外,suspend()方法和resume()方法也被標(biāo)明不推薦使用,這兩個(gè)方法原來用作線程的掛起和恢復(fù)。9.4.2 線程阻塞條件處于運(yùn)行狀態(tài)的線程除了可以進(jìn)入死亡狀態(tài)外,還可能進(jìn)入就緒狀態(tài)和阻塞狀態(tài)。下面分別討論這兩種情況:(1) 運(yùn)行狀態(tài)到就緒狀態(tài)處于運(yùn)行狀態(tài)的線程如果調(diào)用了yield()方法,那么它將放棄CPU時(shí)間,使當(dāng)前正在運(yùn)行的線程進(jìn)入就緒狀態(tài)。這時(shí)有幾種可能的情況:如果沒有其他的線程處于就緒狀態(tài)等待運(yùn)行,該線程會立即繼續(xù)運(yùn)行;如果有等待的線程,此時(shí)線程回到就緒狀態(tài)狀態(tài)與其他線程競爭CPU時(shí)間,當(dāng)有比該線程優(yōu)先級高的線程時(shí),高優(yōu)先級的線程進(jìn)入運(yùn)行狀態(tài),當(dāng)沒有比該線程優(yōu)先級高的線程時(shí),但有同優(yōu)先級的線程,則由線程調(diào)度程序來決定哪個(gè)線程進(jìn)入運(yùn)行狀態(tài),因此線程調(diào)用yield()方法只能將CPU時(shí)間讓給具有同優(yōu)先級的或高優(yōu)先級的線程而不能讓給低優(yōu)先級的線程。一般來說,在調(diào)用線程的yield()方法可以使耗時(shí)的線程暫停執(zhí)行一段時(shí)間,使其他線程有執(zhí)行的機(jī)會。(2) 運(yùn)行狀態(tài)到阻塞狀態(tài)有多種原因可使當(dāng)前運(yùn)行的線程進(jìn)入阻塞狀態(tài),進(jìn)入阻塞狀態(tài)的線程當(dāng)相應(yīng)的事件結(jié)束或條件滿足時(shí)進(jìn)入就緒狀態(tài)。使線程進(jìn)入阻塞狀態(tài)可能有多種原因: 線程調(diào)用了sleep()方法,線程進(jìn)入睡眠狀態(tài),此時(shí)該線程停止執(zhí)行一段時(shí)間。當(dāng)時(shí)間到時(shí)該線程回到就緒狀態(tài),與其他線程競爭CPU時(shí)間。Thread類中定義了一個(gè)interrupt()方法。一個(gè)處于睡眠中的線程若調(diào)用了interrupt()方法,該線程立即結(jié)束睡眠進(jìn)入就緒狀態(tài)。 如果一個(gè)線程的運(yùn)行需要進(jìn)行I/O操作,比如從鍵盤接收數(shù)據(jù),這時(shí)程序可能需要等待用戶的輸入,這時(shí)如果該線程一直占用CPU,其他線程就得不到運(yùn)行。這種情況稱為I/O阻塞。這時(shí)該線程就會離開運(yùn)行狀態(tài)而進(jìn)入阻塞狀態(tài)。Java語言的所有I/O方法都具有這種行為。 有時(shí)要求當(dāng)前線程的執(zhí)行在另一個(gè)線程執(zhí)行結(jié)束后再繼續(xù)執(zhí)行,這時(shí)可以調(diào)用join()方法實(shí)現(xiàn),join()方法有下面三種格式: public void join() throws InterruptedException 使當(dāng)前線程暫停執(zhí)行,等待調(diào)用該方法的線程結(jié)束后再執(zhí)行當(dāng)前線程。 public void join(long millis) throws InterruptedException 最多等待millis毫秒后,當(dāng)前線程繼續(xù)執(zhí)行。 public void join(long millis, int nanos) throws InterruptedException 可以指定多少毫秒、多少納秒后繼續(xù)執(zhí)行當(dāng)前線程。上述方法使當(dāng)前線程暫停執(zhí)行,進(jìn)入阻塞狀態(tài),當(dāng)調(diào)用線程結(jié)束或指定的時(shí)間過后,當(dāng)前線程線程進(jìn)入就緒狀態(tài),例如執(zhí)行下面代碼:t.join();將使當(dāng)前線程進(jìn)入阻塞狀態(tài),當(dāng)線程t執(zhí)行結(jié)束后,當(dāng)前線程才能繼續(xù)執(zhí)行。 線程調(diào)用了wait()方法,等待某個(gè)條件變量,此時(shí)該線程進(jìn)入阻塞狀態(tài)。直到被通知(調(diào)用了notify()或notifyAll()方法)結(jié)束等待后,線程回到就緒狀態(tài)。 另外如果線程不能獲得對象鎖,也進(jìn)入就緒狀態(tài)。后兩種情況在下一節(jié)討論。9.5 線程的同步與共享前面程序中的線程都是獨(dú)立的、異步執(zhí)行的線程。但在很多情況下,多個(gè)線程需要共享數(shù)據(jù)資源,這就涉及到線程的同步與資源共享的問題。9.5.1 資源沖突下面的例子說明,多個(gè)線程共享資源,如果不加以控制可能會產(chǎn)生沖突。程序9.7 CounterTest.javaclass Numprivate int x=0;private int y=0;void increase()x+;y+; void testEqual() System.out.println(x+,+y+:+(x=y);class Counter extends Threadprivate Num num;Counter(Num num) this.num=num;public void run()while(true) num.increase();public class CounterTestpublic static void main(String args)Num num = new Num();Thread count1 = new Counter(num);Thread count2 = new Counter(num);count1.start();count2.start();for(int i=0;i100;i+) num.testEqual();try Thread.sleep(100);catch(InterruptedException e) _上述程序在CounterTest類的main()方法中創(chuàng)建了兩個(gè)線程類Counter的對象count1和count2,這兩個(gè)對象共享一個(gè)Num類的對象num。兩個(gè)線程對象開始運(yùn)行后,都調(diào)用同一個(gè)對象num的increase()方法來增加num對象的x和y的值。在main()方法的for()循環(huán)中輸出num對象的x和y的值。程序輸出結(jié)果有些x、y的值相等,大部分x、y的值不相等。出現(xiàn)上述情況的原因是:兩個(gè)線程對象同時(shí)操作一個(gè)num對象的同一段代碼,通常將這段代碼段稱為臨界區(qū)(critical sections)。在線程執(zhí)行時(shí),可能一個(gè)線程執(zhí)行了x+語句而尚未執(zhí)行y+語句時(shí),系統(tǒng)調(diào)度另一個(gè)線程對象執(zhí)行x+和y+,這時(shí)在主線程中調(diào)用testEqual()方法輸出x、y的值不相等。這里可能出現(xiàn)x的值小于y的值的情況,為什么?9.5.2 對象鎖的實(shí)現(xiàn)上述程序的運(yùn)行結(jié)果說明了多個(gè)線程訪問同一個(gè)對象出現(xiàn)了沖突,為了保證運(yùn)行結(jié)果正確(x、y的值總相等),可以使用Java語言的synchronized關(guān)鍵字,用該關(guān)鍵字修飾方法。用synchronized關(guān)鍵字修飾的方法稱為同步方法,Java平臺為每個(gè)具有synchronized代碼段的對象關(guān)聯(lián)一個(gè)對象鎖(object lock)。這樣任何線程在訪問對象的同步方法時(shí),首先必須獲得對象鎖,然后才能進(jìn)入synchronized方法,這時(shí)其他線程就不能再同時(shí)訪問該對象的同步方法了(包括其他的同步方法)。通常有兩種方法實(shí)現(xiàn)對象鎖:(1) 在方法的聲明中使用synchronized關(guān)鍵字,表明該方法為同步方法。對于上面的程序我們可以在定義Num類的increase()和testEqual()方法時(shí),在它們前面加上synchronized關(guān)鍵字,如下所示:synchronized void increase()x+;y+;synchronized void testEqual()System.out.println(x+,+y+:+(x=y)+:+(xy);一個(gè)方法使用synchronized關(guān)鍵字修飾后,當(dāng)一個(gè)線程調(diào)用該方法時(shí),必須先獲得對象鎖,只有在獲得對象鎖以后才能進(jìn)入synchronized方法。一個(gè)時(shí)刻對象鎖只能被一個(gè)線程持有。如果對象鎖正在被一個(gè)線程持有,其他線程就不能獲得該對象鎖,其他線程就必須等待持有該對象鎖的線程釋放鎖。如果類的方法使用了synchronized關(guān)鍵字修飾,則稱該類對象是線程安全的,否則是線程不安全的。如果只為increase()方法添加synchronized 關(guān)鍵字,結(jié)果還會出現(xiàn)x、y的值不相等的情況,請考慮為什么?(2) 前面實(shí)現(xiàn)對象鎖是在方法前加上synchronized 關(guān)鍵字,這對于我們自己定義的類很容易實(shí)現(xiàn),但如果使用類庫中的類或別人定義的類在調(diào)用一個(gè)沒有使用synchronized關(guān)鍵字修飾的方法時(shí),又要獲得對象鎖,可以使用下面的格式:synchronized(object) /方法調(diào)用假如Num類的increase()方法沒有使用synchronized 關(guān)鍵字,我們在定義Counter類的run()方法時(shí)可以按如下方法使用synchronized為部分代碼加鎖。public void run()while(true)synchronized (num) num.increase(); 同時(shí)在main()方法中調(diào)用testEqual()方法也用synchronized關(guān)鍵字修飾,這樣得到的結(jié)果相同。synchronized(num)num.testEqual();對象鎖的獲得和釋放是由Java運(yùn)行時(shí)系統(tǒng)自動完成的。每個(gè)類也可以有類鎖。類鎖控制對類的synchronized static代碼的訪問。請看下面的例子:public class X static int x, y; static synchronized void foo() x+;y+;當(dāng)foo()方法被調(diào)用時(shí)(如,使用X.foo(),調(diào)用線程必須獲得X類的類鎖。9.5.3 線程間的同步控制在多線程的程序中,除了要防止資源沖突外,有時(shí)還要保證線程的同步。下面通過生產(chǎn)者-消費(fèi)者模型來說明線程的同步與資源共享的問題。假設(shè)有一個(gè)生產(chǎn)者(Producer),一個(gè)消費(fèi)者(Consumer)。生產(chǎn)者產(chǎn)生09的整數(shù),將它們存儲在倉庫(CubbyHole)的對象中并打印出這些數(shù)來;消費(fèi)者從倉庫中取出這些整數(shù)并將其也打印出來。同時(shí)要求生產(chǎn)者產(chǎn)生一個(gè)數(shù)字,消費(fèi)者取得一個(gè)數(shù)字,這就涉及到兩個(gè)線程的同步問題。這個(gè)問題就可以通過兩個(gè)線程實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者,它們共享CubbyHole一個(gè)對象。如果不加控制就得不到預(yù)期的結(jié)果。1. 不同步的設(shè)計(jì)首先我們設(shè)計(jì)用于存儲數(shù)據(jù)的類,該類的定義如下:程序9.8 CubbyHole.javaclass CubbyHole private int content ;public synchronized void put(int value)content = value; public synchronized int get()return content ; _CubbyHole類使用一個(gè)私有成員變量content用來存放整數(shù),put()方法和get()方法用來設(shè)置變量content的值。CubbyHole對象為共享資源,所以用synchronized關(guān)鍵字修飾。當(dāng)put()方法或get()方法被調(diào)用時(shí),線程即獲得了對象鎖,從而可以避免資源沖突。這樣當(dāng)Producer對象調(diào)用put()方法是,它鎖定了該對象,Consumer對象就不能調(diào)用get()方法。當(dāng)put()方法返回時(shí),Producer對象釋放了CubbyHole的鎖。類似地,當(dāng)Consumer對象調(diào)用CubbyHole的get()方法時(shí),它也鎖定該對象,防止Producer對象調(diào)用put()方法。接下來我們看Producer和Consumer的定義,這兩個(gè)類的定義如下:程序9.9 Producer.javapublic class Producer extends Thread private CubbyHole cubbyhole; private int number; public Producer(CubbyHole c, int number) cubbyhole = c; this.number = number; public void run() for (int i = 0; i 10; i+) cubbyhole.put(i); System.out.println(Producer # + this.number + put: + i); try sleep(int)(Math.random() * 100); catch (InterruptedException e) _Producer類中定義了一個(gè)CubbyHole類型的成員變量cubbyhole,它用來存儲產(chǎn)生的整數(shù),另一個(gè)成員變量number用來記錄線程號。這兩個(gè)變量通過構(gòu)造方法傳遞得到。在該類的run()方法中,通過一個(gè)循環(huán)產(chǎn)生10個(gè)整數(shù),每次產(chǎn)生一個(gè)整數(shù),調(diào)用cubbyhole對象的put()方法將其存入該對象中,同時(shí)輸出該數(shù)。下面是Consumer類的定義:程序9.10 Consumer.javapublic class Consumer extends Thread private CubbyHole cubbyhole; private int number; public Consumer(CubbyHole c, int number) cubbyhole = c; this.number = number; public void run() int value = 0; for (int i = 0; i 10; i+) value = cubbyhole.get(); System.out.println(Consumer # + this.number + got: + value); _在Consumer類的run()方法中也是一個(gè)循環(huán),每次調(diào)用cubbyhole的get()方法返回當(dāng)前存儲的整數(shù),然后輸出。下面是主程序,在該程序的main()方法中創(chuàng)建一個(gè)CubbyHole對象c,一個(gè)Producer對象p1,一個(gè)Consumer對象c1,然后啟動兩個(gè)線程。程序9.11 ProducerConsumerTest.javapublic class ProducerConsumerTest public static void main
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 船舶建造材料創(chuàng)新考核試卷
- 石油產(chǎn)品營銷渠道整合優(yōu)化考核試卷
- 玉石加工過程中的能耗與減排考核試卷
- 紡織原料企業(yè)動態(tài)考核試卷
- 營養(yǎng)補(bǔ)充劑批發(fā)商的綠色營銷策略實(shí)施考核試卷
- 航天器空間飛行器對接機(jī)構(gòu)考核試卷
- 起重機(jī)制造材料性能優(yōu)化與選材指導(dǎo)考核試卷
- 森林火災(zāi)撲救安全防護(hù)知識考核試卷
- 淘寶店鋪直播平臺內(nèi)容運(yùn)營合作協(xié)議
- 股權(quán)激勵(lì)行權(quán)協(xié)議(含稅務(wù)籌劃、分紅及股權(quán)激勵(lì)期限延長)
- 2025年4月自考00242民法學(xué)試題及答案含評分標(biāo)準(zhǔn)
- 2025年氫化丁晴橡膠發(fā)展現(xiàn)狀及市場前景趨勢分析
- 2024譯林版七年級英語下冊期中復(fù)習(xí):Unit1-Unit4詞組講義
- 護(hù)士助教面試題及答案
- 《分布式存儲技術(shù)》課件
- 智能化施工流程改進(jìn)技術(shù)措施
- 食品安全管理制度12項(xiàng)餐飲類
- talentq邏輯測試題及答案
- 員工職業(yè)道德與法律意識培訓(xùn)
- 基于S7-200 PLC及MCGS組態(tài)的蘋果分揀機(jī)系統(tǒng)控制設(shè)計(jì)
- 頂名注冊公司協(xié)議書
評論
0/150
提交評論