Java多線程總結(jié).doc_第1頁
Java多線程總結(jié).doc_第2頁
Java多線程總結(jié).doc_第3頁
Java多線程總結(jié).doc_第4頁
Java多線程總結(jié).doc_第5頁
已閱讀5頁,還剩13頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

Java多線程編程總結(jié)Java語言的一個重要特點是內(nèi)在支持多線程的程序設(shè)計。多線程是指在單個的程序內(nèi)可以同時運行多個不同的線程完成不同的任務。多線程的程序設(shè)計具有廣泛的應用。本章主要講授線程的概念、如何創(chuàng)建多線程的程序、線程的生存周期與狀態(tài)的改變、線程的同步與互斥等內(nèi)容。9.1 線程與線程類9.1.1 線程的概念線程的概念來源于計算機的操作系統(tǒng)的進程的概念。進程是一個程序關(guān)于某個數(shù)據(jù)集的一次運行。也就是說,進程是運行中的程序,是程序的一次運行活動。線程和進程的相似之處在于,線程和運行的程序都是單個順序控制流。有些教材將線程稱為輕量級進程(light weight process)。線程被看作是輕量級進程是因為它運行在一個程序的上下文內(nèi),并利用分配給程序的資源和環(huán)境。作為單個順序控制流,線程必須在運行的程序中得到自己運行的資源,如必須有自己的執(zhí)行棧和程序計數(shù)器。線程內(nèi)運行的代碼只能在該上下文內(nèi)。因此還有些教程將執(zhí)行上下文(execution context)作為線程的同義詞。所有的程序員都熟悉順序程序的編寫,如我們編寫的名稱排序和求素數(shù)的程序就是順序程序。順序程序都有開始、執(zhí)行序列和結(jié)束,在程序執(zhí)行的任何時刻,只有一個執(zhí)行點。線程(thread)則是進程中的一個單個的順序控制流。單線程的概念很簡單,如圖9.1所示。多線程(multi-thread)是指在單個的程序內(nèi)可以同時運行多個不同的線程完成不同的任務,圖9.2說明了一個程序中同時有兩個線程運行。一個線程兩個線程圖9.1 單線程程序示意圖 圖9.2 多線程程序示意圖有些程序中需要多個控制流并行執(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);上面的代碼段中,在只支持單線程的語言中,前一個循環(huán)不執(zhí)行完不可能執(zhí)行第二個循環(huán)。要使兩個循環(huán)同時執(zhí)行,需要編寫多線程的程序。很多應用程序是用多線程實現(xiàn)的,如Hot Java Web瀏覽器就是多線程應用的例子。在Hot Java 瀏覽器中,你可以一邊滾動屏幕,一邊下載Applet或圖像,可以同時播放動畫和聲音等。9.1.2 Thread類和Runnable接口多線程是一個程序中可以有多段代碼同時運行,那么這些代碼寫在哪里,如何創(chuàng)建線程對象呢?首先,我們來看Java語言實現(xiàn)多線程編程的類和接口。在java.lang包中定義了Runnable接口和Thread類。Runnable接口中只定義了一個方法,它的格式為: public abstract void run() 這個方法要由實現(xiàn)了Runnable接口的類實現(xiàn)。Runnable對象稱為可運行對象,一個線程的運行就是執(zhí)行該對象的run()方法。Thread類實現(xiàn)了Runnable接口,因此Thread對象也是可運行對象。同時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為線程運行的目標對象,即線程調(diào)用start()方法啟動后運行那個對象的run()方法,該對象的類型為Runnable,若沒有指定目標對象,則以當前類對象為目標對象;name為線程名,group指定線程屬于哪個線程組(有關(guān)線程組的概念請參考9.6節(jié))。Thread類的常用方法有: public static Thread currentThread() 返回當前正在執(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使當前正在執(zhí)行的線程暫時停止執(zhí)行指定的毫秒時間。指定時間過后,線程繼續(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() 使當前執(zhí)行的線程暫停執(zhí)行,允許其他線程執(zhí)行。 public ThreadGroup getThreadGroup() 返回該線程所屬的線程組對象。 public void interrupt() 中斷當前線程。 public boolean isAlive() 返回指定線程是否處于活動狀態(tài)。9.2 線程的創(chuàng)建本節(jié)介紹如何創(chuàng)建和運行線程的兩種方法。線程運行的代碼就是實現(xiàn)了Runnable接口的類的run()方法或者是Thread類的子類的run()方法,因此構(gòu)造線程體就有兩種方法: 繼承Thread類并覆蓋它的run()方法; 實現(xiàn)Runnable接口并實現(xiàn)它的run()方法。9.2.1 繼承Thread類創(chuàng)建線程通過繼承Thread類,并覆蓋run()方法,這時就可以用該類的實例作為線程的目標對象。下面的程序定義了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)建了兩個SimpleThread類的線程對象并調(diào)用線程類的start()方法啟動線程。構(gòu)造線程時沒有指定目標對象,所以線程啟動后執(zhí)行本類的run()方法。注意,實際上ThreadTest程序中有三個線程同時運行。請試著將下段代碼加到main()方法中,分析程序運行結(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é)果可以看到,在應用程序的main()方法啟動時,JVM就創(chuàng)建一個主線程,在主線程中可以創(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()方法中聲明了一個Thread對象t,然后調(diào)用Thread類的靜態(tài)方法currentThread()獲得當前線程對象。然后重新設(shè)置該線程對象的名稱,最后輸出線程對象、線程組對象名和線程對象名。9.2.2 實現(xiàn)Runnable接口創(chuàng)建線程可以定義一個類實現(xiàn)Runnable接口,然后將該類對象作為線程的目標對象。實現(xiàn)Runnable接口就是實現(xiàn)run()方法。下面程序通過實現(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(); _下面是一個小應用程序,利用線程對象在其中顯示當前時間。程序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) _該小應用程序的運行結(jié)果如圖9.3所示:圖9.3 ClockDemo的運行結(jié)果9.3 線程的狀態(tài)與調(diào)度9.3.1 線程的生命周期線程從創(chuàng)建、運行到結(jié)束總是處于下面五個狀態(tài)之一:新建狀態(tài)、就緒狀態(tài)、運行狀態(tài)、阻塞狀態(tài)及死亡狀態(tài)。線程的狀態(tài)如圖9.4所示:新建狀態(tài)就緒狀態(tài)阻塞狀態(tài)運行狀態(tài)死亡狀態(tài)圖9.4 線程的五種狀態(tài)下面以前面的Java小程序為例說明線程的狀態(tài):1. 新建狀態(tài)(New Thread)當Applet啟動時調(diào)用Applet的start()方法,此時小應用程序就創(chuàng)建一個Thread對象clockThread。 public void start() if (clockThread = null) clockThread = new Thread(cp, Clock); clockThread.start(); 當該語句執(zhí)行后clockThread就處于新建狀態(tài)。處于該狀態(tài)的線程僅僅是空的線程對象,并沒有為其分配系統(tǒng)資源。當線程處于該狀態(tài),你僅能啟動線程,調(diào)用任何其他方法是無意義的且會引發(fā)IllegalThreadStateException異常(實際上,當調(diào)用線程的狀態(tài)所不允許的任何方法時,運行時系統(tǒng)都會引發(fā)IllegalThreadStateException異常)。 注意cp作為線程構(gòu)造方法的第一個參數(shù),該參數(shù)必須是實現(xiàn)了Runnable接口的對象并提供線程運行的run()方法,第二個參數(shù)是線程名。2. 就緒狀態(tài)(Runnable)一個新創(chuàng)建的線程并不自動開始運行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當線程對象調(diào)用start()方法即啟動了線程,如clockThread.start(); 語句就是啟動clockThread線程。start()方法創(chuàng)建線程運行的系統(tǒng)資源,并調(diào)度線程運行run()方法。當start()方法返回后,線程就處于就緒狀態(tài)。處于就緒狀態(tài)的線程并不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。因為在單CPU的計算機系統(tǒng)中,不可能同時運行多個線程,一個時刻僅有一個線程處于運行狀態(tài)。因此此時可能有多個線程處于就緒狀態(tài)。對多個處于就緒狀態(tài)的線程是由Java運行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。3. 運行狀態(tài)(Running) 當線程獲得CPU時間后,它才進入運行狀態(tài),真正開始執(zhí)行run()方法,這里run()方法中是一個循環(huán),循環(huán)條件是true。 public void run() while (true) repaint(); try Thread.sleep(1000); catch (InterruptedException e) 4. 阻塞狀態(tài)(Blocked)線程運行過程中,可能由于各種原因進入阻塞狀態(tài)。所謂阻塞狀態(tài)是正在運行的線程沒有運行結(jié)束,暫時讓出CPU,這時其他處于就緒狀態(tài)的線程就可以獲得CPU時間,進入運行狀態(tài)。有關(guān)阻塞狀態(tài)在后面詳細討論。5. 死亡狀態(tài)(Dead)線程的正常結(jié)束,即run()方法返回,線程運行就結(jié)束了,此時線程就處于死亡狀態(tài)。本例子中,線程運行結(jié)束的條件是clockThread為null,而在小應用程序的stop()方法中,將clockThread賦值為null。即當用戶離開含有該小應用程序的頁面時,瀏覽器調(diào)用stop()方法,將clockThread賦值為null,這樣在run()的while循環(huán)時條件就為false,這樣線程運行就結(jié)束了。如果再重新訪問該頁面,小應用程序的start()方法又會重新被調(diào)用,重新創(chuàng)建并啟動一個新的線程。 public void stop() clockThread = null; 程序不能像終止小應用程序那樣通過調(diào)用一個方法來結(jié)束線程(小應用程序通過調(diào)用stop()方法結(jié)束小應用程序的運行)。線程必須通過run()方法的自然結(jié)束而結(jié)束。通常在run()方法中是一個循環(huán),要么是循環(huán)結(jié)束,要么是循環(huán)的條件不滿足,這兩種情況都可以使線程正常結(jié)束,進入死亡狀態(tài)。例如,下面一段代碼是一個循環(huán):public void run() int i = 0; while(i100) i+; System.out.println(i = + i );當該段代碼循環(huán)結(jié)束后,線程就自然結(jié)束了。注意一個處于死亡狀態(tài)的線程不能再調(diào)用該線程的任何方法。9.3.2 線程的優(yōu)先級和調(diào)度Java的每個線程都有一個優(yōu)先級,當有多個線程處于就緒狀態(tài)時,線程調(diào)度程序根據(jù)線程的優(yōu)先級調(diào)度線程運行??梢杂孟旅娣椒ㄔ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ōu)先級的1、5和10,數(shù)值越大優(yōu)先級越高。當創(chuàng)建Java線程時,如果沒有指定它的優(yōu)先級,則它從創(chuàng)建該線程那里繼承優(yōu)先級。一般來說,只有在當前線程停止或由于某種原因被阻塞,較低優(yōu)先級的線程才有機會運行。前面說過多個線程可并發(fā)運行,然而實際上并不總是這樣。由于很多計算機都是單CPU的,所以一個時刻只能有一個線程運行,多個線程的并發(fā)運行只是幻覺。在單CPU機器上多個線程的執(zhí)行是按照某種順序執(zhí)行的,這稱為線程的調(diào)度(scheduling)。大多數(shù)計算機僅有一個CPU,所以線程必須與其他線程共享CPU。多個線程在單個CPU是按照某種順序執(zhí)行的。實際的調(diào)度策略隨系統(tǒng)的不同而不同,通常線程調(diào)度可以采用兩種策略調(diào)度處于就緒狀態(tài)的線程。(1) 搶占式調(diào)度策略 Java運行時系統(tǒng)的線程調(diào)度算法是搶占式的 (preemptive)。Java運行時系統(tǒng)支持一種簡單的固定優(yōu)先級的調(diào)度算法。如果一個優(yōu)先級比其他任何處于可運行狀態(tài)的線程都高的線程進入就緒狀態(tài),那么運行時系統(tǒng)就會選擇該線程運行。新的優(yōu)先級較高的線程搶占(preempt)了其他線程。但是Java運行時系統(tǒng)并不搶占同優(yōu)先級的線程。換句話說,Java運行時系統(tǒng)不是分時的(time-slice)。然而,基于Java Thread類的實現(xiàn)系統(tǒng)可能是支持分時的,因此編寫代碼時不要依賴分時。當系統(tǒng)中的處于就緒狀態(tài)的線程都具有相同優(yōu)先級時,線程調(diào)度程序采用一種簡單的、非搶占式的輪轉(zhuǎn)的調(diào)度順序。(2) 時間片輪轉(zhuǎn)調(diào)度策略有些系統(tǒng)的線程調(diào)度采用時間片輪轉(zhuǎn)(round-robin)調(diào)度策略。這種調(diào)度策略是從所有處于就緒狀態(tài)的線程中選擇優(yōu)先級最高的線程分配一定的CPU時間運行。該時間過后再選擇其他線程運行。只有當線程運行結(jié)束、放棄(yield)CPU或由于某種原因進入阻塞狀態(tài),低優(yōu)先級的線程才有機會執(zhí)行。如果有兩個優(yōu)先級相同的線程都在等待CPU,則調(diào)度程序以輪轉(zhuǎn)的方式選擇運行的線程。9.4 線程狀態(tài)的改變新建狀態(tài)一個線程在其生命周期中可以從一種狀態(tài)改變到另一種狀態(tài),線程狀態(tài)的變遷如圖9.5所示:yield()run()schedulerstart()死亡狀態(tài)運行狀態(tài)就緒狀態(tài)阻塞狀態(tài)sleep()I/O操作join()wait()圖9.5 線程狀態(tài)的改變9.4.1 控制線程的啟動和結(jié)束當一個新建的線程調(diào)用它的start()方法后即進入就緒狀態(tài),處于就緒狀態(tài)的線程被線程調(diào)度程序選中就可以獲得CPU時間,進入運行狀態(tài),該線程就開始運行run()方法??刂凭€程的結(jié)束稍微復雜一點。如果線程的run()方法是一個確定次數(shù)的循環(huán),則循環(huán)結(jié)束后,線程運行就結(jié)束了,線程對象即進入死亡狀態(tài)。如果run()方法是一個不確定循環(huán),早期的方法是調(diào)用線程對象的stop()方法,然而由于該方法可能導致線程死鎖,因此從1.1版開始,不推薦使用該方法結(jié)束線程。一般是通過設(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類中定義了一個布而變量flag,同時定義了一個stopRun()方法,在其中將該變量設(shè)置為false。在主程序中通過調(diào)用該方法,從而改變該變量的值,使得run()方法的while循環(huán)條件不滿足,從而實現(xiàn)結(jié)束線程的運行。說明 在Thread類中除了stop()方法被標注為不推薦(deprecated) 使用外,suspend()方法和resume()方法也被標明不推薦使用,這兩個方法原來用作線程的掛起和恢復。9.4.2 線程阻塞條件處于運行狀態(tài)的線程除了可以進入死亡狀態(tài)外,還可能進入就緒狀態(tài)和阻塞狀態(tài)。下面分別討論這兩種情況:(1) 運行狀態(tài)到就緒狀態(tài)處于運行狀態(tài)的線程如果調(diào)用了yield()方法,那么它將放棄CPU時間,使當前正在運行的線程進入就緒狀態(tài)。這時有幾種可能的情況:如果沒有其他的線程處于就緒狀態(tài)等待運行,該線程會立即繼續(xù)運行;如果有等待的線程,此時線程回到就緒狀態(tài)狀態(tài)與其他線程競爭CPU時間,當有比該線程優(yōu)先級高的線程時,高優(yōu)先級的線程進入運行狀態(tài),當沒有比該線程優(yōu)先級高的線程時,但有同優(yōu)先級的線程,則由線程調(diào)度程序來決定哪個線程進入運行狀態(tài),因此線程調(diào)用yield()方法只能將CPU時間讓給具有同優(yōu)先級的或高優(yōu)先級的線程而不能讓給低優(yōu)先級的線程。一般來說,在調(diào)用線程的yield()方法可以使耗時的線程暫停執(zhí)行一段時間,使其他線程有執(zhí)行的機會。(2) 運行狀態(tài)到阻塞狀態(tài)有多種原因可使當前運行的線程進入阻塞狀態(tài),進入阻塞狀態(tài)的線程當相應的事件結(jié)束或條件滿足時進入就緒狀態(tài)。使線程進入阻塞狀態(tài)可能有多種原因: 線程調(diào)用了sleep()方法,線程進入睡眠狀態(tài),此時該線程停止執(zhí)行一段時間。當時間到時該線程回到就緒狀態(tài),與其他線程競爭CPU時間。Thread類中定義了一個interrupt()方法。一個處于睡眠中的線程若調(diào)用了interrupt()方法,該線程立即結(jié)束睡眠進入就緒狀態(tài)。 如果一個線程的運行需要進行I/O操作,比如從鍵盤接收數(shù)據(jù),這時程序可能需要等待用戶的輸入,這時如果該線程一直占用CPU,其他線程就得不到運行。這種情況稱為I/O阻塞。這時該線程就會離開運行狀態(tài)而進入阻塞狀態(tài)。Java語言的所有I/O方法都具有這種行為。 有時要求當前線程的執(zhí)行在另一個線程執(zhí)行結(jié)束后再繼續(xù)執(zhí)行,這時可以調(diào)用join()方法實現(xiàn),join()方法有下面三種格式: public void join() throws InterruptedException 使當前線程暫停執(zhí)行,等待調(diào)用該方法的線程結(jié)束后再執(zhí)行當前線程。 public void join(long millis) throws InterruptedException 最多等待millis毫秒后,當前線程繼續(xù)執(zhí)行。 public void join(long millis, int nanos) throws InterruptedException 可以指定多少毫秒、多少納秒后繼續(xù)執(zhí)行當前線程。上述方法使當前線程暫停執(zhí)行,進入阻塞狀態(tài),當調(diào)用線程結(jié)束或指定的時間過后,當前線程線程進入就緒狀態(tài),例如執(zhí)行下面代碼:t.join();將使當前線程進入阻塞狀態(tài),當線程t執(zhí)行結(jié)束后,當前線程才能繼續(xù)執(zhí)行。 線程調(diào)用了wait()方法,等待某個條件變量,此時該線程進入阻塞狀態(tài)。直到被通知(調(diào)用了notify()或notifyAll()方法)結(jié)束等待后,線程回到就緒狀態(tài)。 另外如果線程不能獲得對象鎖,也進入就緒狀態(tài)。后兩種情況在下一節(jié)討論。9.5 線程的同步與共享前面程序中的線程都是獨立的、異步執(zhí)行的線程。但在很多情況下,多個線程需要共享數(shù)據(jù)資源,這就涉及到線程的同步與資源共享的問題。9.5.1 資源沖突下面的例子說明,多個線程共享資源,如果不加以控制可能會產(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)建了兩個線程類Counter的對象count1和count2,這兩個對象共享一個Num類的對象num。兩個線程對象開始運行后,都調(diào)用同一個對象num的increase()方法來增加num對象的x和y的值。在main()方法的for()循環(huán)中輸出num對象的x和y的值。程序輸出結(jié)果有些x、y的值相等,大部分x、y的值不相等。出現(xiàn)上述情況的原因是:兩個線程對象同時操作一個num對象的同一段代碼,通常將這段代碼段稱為臨界區(qū)(critical sections)。在線程執(zhí)行時,可能一個線程執(zhí)行了x+語句而尚未執(zhí)行y+語句時,系統(tǒng)調(diào)度另一個線程對象執(zhí)行x+和y+,這時在主線程中調(diào)用testEqual()方法輸出x、y的值不相等。這里可能出現(xiàn)x的值小于y的值的情況,為什么?9.5.2 對象鎖的實現(xiàn)上述程序的運行結(jié)果說明了多個線程訪問同一個對象出現(xiàn)了沖突,為了保證運行結(jié)果正確(x、y的值總相等),可以使用Java語言的synchronized關(guān)鍵字,用該關(guān)鍵字修飾方法。用synchronized關(guān)鍵字修飾的方法稱為同步方法,Java平臺為每個具有synchronized代碼段的對象關(guān)聯(lián)一個對象鎖(object lock)。這樣任何線程在訪問對象的同步方法時,首先必須獲得對象鎖,然后才能進入synchronized方法,這時其他線程就不能再同時訪問該對象的同步方法了(包括其他的同步方法)。通常有兩種方法實現(xiàn)對象鎖:(1) 在方法的聲明中使用synchronized關(guān)鍵字,表明該方法為同步方法。對于上面的程序我們可以在定義Num類的increase()和testEqual()方法時,在它們前面加上synchronized關(guān)鍵字,如下所示:synchronized void increase()x+;y+;synchronized void testEqual()System.out.println(x+,+y+:+(x=y)+:+(xy);一個方法使用synchronized關(guān)鍵字修飾后,當一個線程調(diào)用該方法時,必須先獲得對象鎖,只有在獲得對象鎖以后才能進入synchronized方法。一個時刻對象鎖只能被一個線程持有。如果對象鎖正在被一個線程持有,其他線程就不能獲得該對象鎖,其他線程就必須等待持有該對象鎖的線程釋放鎖。如果類的方法使用了synchronized關(guān)鍵字修飾,則稱該類對象是線程安全的,否則是線程不安全的。如果只為increase()方法添加synchronized 關(guān)鍵字,結(jié)果還會出現(xiàn)x、y的值不相等的情況,請考慮為什么?(2) 前面實現(xiàn)對象鎖是在方法前加上synchronized 關(guān)鍵字,這對于我們自己定義的類很容易實現(xiàn),但如果使用類庫中的類或別人定義的類在調(diào)用一個沒有使用synchronized關(guān)鍵字修飾的方法時,又要獲得對象鎖,可以使用下面的格式:synchronized(object) /方法調(diào)用假如Num類的increase()方法沒有使用synchronized 關(guān)鍵字,我們在定義Counter類的run()方法時可以按如下方法使用synchronized為部分代碼加鎖。public void run()while(true)synchronized (num) num.increase(); 同時在main()方法中調(diào)用testEqual()方法也用synchronized關(guān)鍵字修飾,這樣得到的結(jié)果相同。synchronized(num)num.testEqual();對象鎖的獲得和釋放是由Java運行時系統(tǒng)自動完成的。每個類也可以有類鎖。類鎖控制對類的synchronized static代碼的訪問。請看下面的例子:public class X static int x, y; static synchronized void foo() x+;y+;當foo()方法被調(diào)用時(如,使用X.foo(),調(diào)用線程必須獲得X類的類鎖。9.5.3 線程間的同步控制在多線程的程序中,除了要防止資源沖突外,有時還要保證線程的同步。下面通過生產(chǎn)者-消費者模型來說明線程的同步與資源共享的問題。假設(shè)有一個生產(chǎn)者(Producer),一個消費者(Consumer)。生產(chǎn)者產(chǎn)生09的整數(shù),將它們存儲在倉庫(CubbyHole)的對象中并打印出這些數(shù)來;消費者從倉庫中取出這些整數(shù)并將其也打印出來。同時要求生產(chǎn)者產(chǎn)生一個數(shù)字,消費者取得一個數(shù)字,這就涉及到兩個線程的同步問題。這個問題就可以通過兩個線程實現(xiàn)生產(chǎn)者和消費者,它們共享CubbyHole一個對象。如果不加控制就得不到預期的結(jié)果。1. 不同步的設(shè)計首先我們設(shè)計用于存儲數(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類使用一個私有成員變量content用來存放整數(shù),put()方法和get()方法用來設(shè)置變量content的值。CubbyHole對象為共享資源,所以用synchronized關(guān)鍵字修飾。當put()方法或get()方法被調(diào)用時,線程即獲得了對象鎖,從而可以避免資源沖突。這樣當Producer對象調(diào)用put()方法是,它鎖定了該對象,Consumer對象就不能調(diào)用get()方法。當put()方法返回時,Producer對象釋放了CubbyHole的鎖。類似地,當Consumer對象調(diào)用CubbyHole的get()方法時,它也鎖定該對象,防止Producer對象調(diào)用put()方法。接下來我們看Producer和Consumer的定義,這兩個類的定義如下:程序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類中定義了一個CubbyHole類型的成員變量cubbyhole,它用來存儲產(chǎn)生的整數(shù),另一個成員變量number用來記錄線程號。這兩個變量通過構(gòu)造方法傳遞得到。在該類的run()方法中,通過一個循環(huán)產(chǎn)生10個整數(shù),每次產(chǎn)生一個整數(shù),調(diào)用cubbyhole對象的put()方法將其存入該對象中,同時輸出該數(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()方法中也是一個循環(huán),每次調(diào)用cubbyhole的get()方法返回當前存儲的整數(shù),然后輸出。下面是主程序,在該程序的main()方法中創(chuàng)建一個CubbyHole對象c,一個Producer對象p1,一個Consumer對象c1,然后啟動兩個線程。程序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)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論