1. 程式人生 > >60 JAVA程式設計思想——多執行緒

60 JAVA程式設計思想——多執行緒

60.JAVA程式設計思想——多執行緒

利用物件,可將一個程式分割成相互獨立的區域。我們通常也需要將一個程式轉換成多個獨立執行的子任務。象這樣的每個子任務都叫作一個“執行緒”(Thread)。編寫程式時,可將每個執行緒都想象成獨立執行,而且都有自己的專用CPU。一些基礎機制實際會為我們自動分割CPU 的時間。我們通常不必關心這些細節問題,所以多執行緒的程式碼編寫是相當簡便的。

這時理解一些定義對以後的學習狠有幫助。“程序”是指一種“自包容”的執行程式,有自己的地址空間。“多工”作業系統能同時執行多個程序(程式)——但實際是由於CPU 分時機制的作用,使每個程序都能迴圈獲得自己的CPU 時間片。但由於輪換速度非常快,使得所有程式好象是在“同時”執行一樣。“執行緒”是程序內部單一的一個順序控制流。因此,一個程序可能容納了多個同時執行的執行緒。

多執行緒的應用範圍很廣。但在一般情況下,程式的一些部分同特定的事件或資源聯絡在一起,同時又不想為它而暫停程式其他部分的執行。這樣一來,就可考慮建立一個執行緒,令其與那個事件或資源關聯到一起,並讓它獨立於主程式執行。一個很好的例子便是“Quit”或“退出”按鈕——我們並不希望在程式的每一部分程式碼中都輪詢這個按鈕,同時又希望該按鈕能及時地作出響應(使程式看起來似乎經常都在輪詢它)。事實上,多執行緒最主要的一個用途就是構建一個“反應靈敏”的使用者介面。

1     反應靈敏的使用者介面

一個需要執行某些CPU 密集型計算的程式。由於CPU“全心全意”為那些計算服務,所以對使用者的輸入十分遲鈍,幾乎沒有什麼反應。

1.1     程式碼

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public class Counter1 extends Applet {

    private intcount= 0;

    private Button onOff= newButton("Toggle"),start= newButton("Start");

    private

TextField t= newTextField(10);

    private booleanrunFlag= true;

    public voidinit() {

        add(t);

        start.addActionListener(new StartL());

        add(start);

        onOff.addActionListener(new OnOffL());

        add(onOff);

    }

    public voidgo() {

        while (true) {

            try {

                Thread.currentThread().sleep(100);

            } catch (InterruptedException e) {

            }

            if (runFlag)

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

 

        }

    }

 

    class StartL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            go();

        }

    }

 

    class OnOffL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            runFlag = !runFlag;

        }

    }

 

    public staticvoidmain(String[] args){

        Counter1 applet = new Counter1();

        Frame aFrame = new Frame("Counter1");

        aFrame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

1.2     執行

在這個程式中,AWT 和程式片程式碼都應是大家熟悉的。go()方法正是程式全心全意服務的對待:將當前的count(計數)值置入TextField(文字欄位)t,然後使count 增值。go()內的部分無限迴圈是呼叫sleep()。sleep()必須同一個Thread(執行緒)物件關聯到一起,而且似乎每個

應用程式都有部分執行緒同它關聯(事實上,Java 本身就是建立線上程基礎上的,肯定有一些執行緒會伴隨我們寫的應用一起執行)。所以無論我們是否明確使用了執行緒,都可利用Thread.currentThread()產生由程式使用的當前執行緒,然後為那個執行緒呼叫sleep()。注意,Thread.currentThread()是Thread 類的一個靜態方法。

注意sleep()可能“擲”出一個InterruptException(中斷違例)——儘管產生這樣的違例被認為是中止執行緒的一種“惡意”手段,而且應該儘可能地杜絕這一做法。再次提醒大家,違例是為異常情況而產生的,而不是為了正常的控制流。在這裡包含了對一個“睡眠”執行緒的中斷,以支援未來的一種語言特性。

一旦按下start 按鈕,就會呼叫go()。研究一下go(),你可能會很自然地(就象我一樣)認為它該支援多執行緒,因為它會進入“睡眠”狀態。也就是說,儘管方法本身“睡著”了,CPU 仍然應該忙於監視其他按鈕“按下”事件。但有一個問題,那就是go()是永遠不會返回的,因為它被設計成一個無限迴圈。這意味著actionPerformed()根本不會返回。由於在第一個按鍵以後便陷入actionPerformed()中,所以程式不能再對其他任何事件進行控制(如果想出來,必須以某種方式“殺死”程序——最簡便的方式就是在控制檯視窗按Ctrl+C 鍵)。

這裡最基本的問題是go()需要繼續執行自己的操作,而與此同時,它也需要返回,以便actionPerformed()能夠完成,而且使用者介面也能繼續響應使用者的操作。但物件go()這樣的傳統方法來說,它卻不能在繼續的同時將控制權返回給程式的其他部分。這聽起來似乎是一件不可能做到的事情,就象CPU 必須同時位於兩個地方一樣,但執行緒可以解決一切。“執行緒模型”(以及Java 中的程式設計支援)是一種程式編寫規範,可在單獨一個程式裡實現幾個操作的同時進行。根據這一機制,CPU 可為每個執行緒都分配自己的一部分時間。每個執行緒都“感覺”自己好象擁有整個CPU,但CPU 的計算時間實際卻是在所有執行緒間分攤的。

執行緒機制多少降低了一些計算效率,但無論程式的設計,資源的均衡,還是使用者操作的方便性,都從中獲得了巨大的利益。綜合考慮,這一機制是非常有價值的。當然,如果本來就安裝了多塊CPU,那麼作業系統能夠自行決定為不同的CPU 分配哪些執行緒,程式的總體執行速度也會變得更快(所有這些都要求作業系統以及應用程式的支援)。多執行緒和多工是充分發揮多處理機系統能力的一種最有效的方式。

2     從執行緒繼承

為建立一個執行緒,最簡單的方法就是從Thread 類繼承。這個類包含了建立和執行執行緒所需的一切東西。

Thread 最重要的方法是run()。但為了使用run(),必須對其進行過載或者覆蓋,使其能充分按自己的吩咐行事。因此,run()屬於那些會與程式中的其他執行緒“併發”或“同時”執行的程式碼。下面這個例子可建立任意數量的執行緒,並通過為每個執行緒分配一個獨一無二的編號(由一個靜態變數產生),從而對不同的執行緒進行跟蹤。Thread 的run()方法在這裡得到了覆蓋,每通過一次迴圈,計數就減1——計數為0 時則完成迴圈(此時一旦返回run(),執行緒就中止執行)。

2.1     程式碼

public class SimpleThread extends Thread {

    private intcountDown= 5;

    private intthreadNumber;

    private staticintthreadCount= 0;

 

    public SimpleThread() {

        threadNumber = ++threadCount;

        System.out.println("Making " + threadNumber);

    }

 

    public voidrun() {

        while (true) {

            System.out.println("Thread "+ threadNumber+ "("+ countDown+ ")");

            if (--countDown == 0)

                return;

        }

    }

 

    public staticvoidmain(String[] args){

        for (int i = 0; i < 5; i++)

            new SimpleThread().start();

        System.out.println("All Threads Started");

    }

} /// :~

2.2     執行

run()方法幾乎肯定含有某種形式的迴圈——它們會一直持續到執行緒不再需要為止。因此,我們必須規定特定的條件,以便中斷並退出這個迴圈(或者在上述的例子中,簡單地從run()返回即可)。run()通常採用一種無限迴圈的形式。也就是說,通過阻止外部發出對執行緒的stop()或者destroy()呼叫,它會永遠執行下去(直到程式完成)。

在main()中,可看到建立並運行了大量執行緒。Thread 包含了一個特殊的方法,叫作start(),它的作用是對執行緒進行特殊的初始化,然後呼叫run()。所以整個步驟包括:呼叫構建器來構建物件,然後用start()配置執行緒,再呼叫run()。如果不呼叫start()——如果適當的話,可在構建器那樣做——執行緒便永遠不會啟動。

下面是該程式某一次執行的輸出(注意每次執行都會不同):

可注意到這個例子中到處都呼叫了sleep(),然而輸出結果指出每個執行緒都獲得了屬於自己的那一部分CPU執行時間。從中可以看出,儘管sleep()依賴一個執行緒的存在來執行,但卻與允許或禁止執行緒無關。它只不過是另一個不同的方法而已。

亦可看出執行緒並不是按它們建立時的順序執行的。事實上,CPU 處理一個現有執行緒集的順序是不確定的——除非我們親自介入,並用Thread 的setPriority()方法調整它們的優先順序。

main()建立Thread 物件時,它並未捕獲任何一個物件的控制代碼。普通物件對於垃圾收集來說是一種“公平競賽”,但執行緒卻並非如此。每個執行緒都會“註冊”自己,所以某處實際存在著對它的一個引用。這樣一來,垃圾收集器便只好對它“瞠目以對”了。

3     針對使用者介面的多執行緒

現在,我們也許能用一個執行緒解決在Counter1.java 中出現的問題。採用的一個技巧便是在一個執行緒的run()方法中放置“子任務”——亦即位於go()內的迴圈。一旦使用者按下Start 按鈕,執行緒就會啟動,但馬上結束執行緒的建立。這樣一來,儘管執行緒仍在執行,但程式的主要工作卻能得以繼續(等候並響應使用者介面的事件)。

3.1     程式碼

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

 

class SeparateSubTask extends Thread {

    private intcount= 0;

    private Counter2 c2;

    private booleanrunFlag= true;

 

    public SeparateSubTask(Counter2 c2) {

        this.c2 = c2;

        start();

    }

 

    public voidinvertFlag() {

        runFlag = !runFlag;

    }

 

    public voidrun() {

        while (true) {

            try {

                sleep(100);

            } catch (InterruptedException e) {

            }

            if (runFlag)

                c2.t.setText(Integer.toString(count++));

        }

    }

}

 

public class Counter2 extends Applet {

    TextField t = new TextField(10);

    private SeparateSubTask sp = null;

    private Button onOff= newButton("Toggle"),start= newButton("Start");

 

    public voidinit() {

        add(t);

        start.addActionListener(new StartL());

        add(start);

        onOff.addActionListener(new OnOffL());

        add(onOff);

    }

 

    class StartL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            if (sp == null)

                sp = new SeparateSubTask(Counter2.this);

        }

    }

 

    class OnOffL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            if (sp != null)

                sp.invertFlag();

        }

    }

 

    public staticvoidmain(String[] args){

        Counter2 applet = new Counter2();

        Frame aFrame = new Frame("Counter2");

        aFrame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

3.2     執行

現在,Counter2 變成了一個相當直接的程式,它的唯一任務就是設定並管理使用者介面。但假若使用者現在按下Start 按鈕,卻不會真正呼叫一個方法。此時不是建立類的一個執行緒,而是建立SeparateSubTask,然後繼續Counter2 事件迴圈。注意此時會儲存SeparateSubTask 的控制代碼,以便我們按下onOff 按鈕的時候,能正常地切換位於SeparateSubTask 內部的runFlag(執行標誌)。隨後那個執行緒便可啟動(當它看到標誌的時候),然後將自己中止(亦可將SeparateSubTask 設為一個內部類來達到這一目的)。

SeparateSubTask 類是對Thread 的一個簡單擴充套件,它帶有一個構建器(其中儲存了Counter2 控制代碼,然後通過呼叫start()來執行執行緒)以及一個run()——本質上包含了Counter1.java 的go()內的程式碼。由於SeparateSubTask 知道自己容納了指向一個Counter2 的控制代碼,所以能夠在需要的時候介入,並訪問Counter2的TestField(文字欄位)。

按下onOff 按鈕,幾乎立即能得到正確的響應。當然,這個響應其實並不是“立即”發生的,它畢竟和那種由“中斷”驅動的系統不同。只有執行緒擁有CPU 的執行時間,並注意到標記已發生改變,計數器才會停止。

4     用內部類改善程式碼

SeparateSubTask 和Counter2 類之間發生的結合行為。SeparateSubTask同Counter2 “親密”地結合到了一起——它必須持有指向自己“父”Counter2 物件的一個控制代碼,以便自己能回撥和操縱它。但兩個類並不是真的合併為單獨一個類(儘管在下一節中,我們會講到Java 確實提供了合併它們的方法),因為它們各自做的是不同的事情,而且是在不同的時間建立的。但不管怎樣,它們依然緊密地結合到一起(更準確地說,應該叫“聯合”),所以使程式程式碼多少顯得有些笨拙。在這種情況下,一個內部類可以顯著改善程式碼的“可讀性”和執行效率:

4.1     程式碼2

importjava.awt.*;

importjava.awt.event.*;

import java.applet.*;

 

public class Counter2iextends Applet {

    private classSeparateSubTask extends Thread{

        int count =0;

        boolean runFlag = true;

 

        SeparateSubTask() {

            start();

        }

 

        public voidrun() {

            while (true) {

                try {

                    sleep(100);

                }catch (InterruptedException e) {

                }

                if (runFlag)

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

            }

        }

    }

 

    privateSeparateSubTask sp = null;

    privateTextField t = newTextField(10);

    privateButton onOff = newButton("Toggle"), start = newButton("Start");

 

    public voidinit() {

        add(t);

        start.addActionListener(newStartL());

        add(start);

        onOff.addActionListener(newOnOffL());

        add(onOff);

    }

 

    classStartL implements ActionListener {

        public voidactionPerformed(ActionEvent e) {

            if (sp == null)

                sp = newSeparateSubTask();

        }

    }

 

    classOnOffL implements ActionListener {

        public voidactionPerformed(ActionEvent e) {

            if (sp != null)

                sp.runFlag = !sp.runFlag; //invertFlag();

        }

    }

 

    public static voidmain(String[] args) {

        Counter2i applet = newCounter2i();

        Frame aFrame = newFrame("Counter2i");

        aFrame.addWindowListener(newWindowAdapter() {

            public voidwindowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        aFrame.add(applet,BorderLayout.CENTER);

        aFrame.setSize(300,200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} ///:~

4.2     執行

這個SeparateSubTask 名字不會與前例中的SeparateSubTask 衝突——即使它們都在相同的目錄裡——因為它已作為一個內部類隱藏起來。大家亦可看到內部類被設為private(私有)屬性,這意味著它的欄位和方法都可獲得預設的訪問許可權(run()除外,它必須設為public,因為它在基礎類中是公開的)。除Counter2i之外,其他任何方面都不可訪問private 內部類。而且由於兩個類緊密結合在一起,所以很容易放寬它們之間的訪問限制。在SeparateSubTask 中,我們可看到invertFlag()方法已被刪去,因為Counter2i 現在可以直接訪問runFlag。

此外,注意SeparateSubTask 的構建器已得到了簡化——它現在唯一的用外就是啟動執行緒。Counter2i 物件的控制代碼仍象以前那樣得以捕獲,但不再是通過人工傳遞和引用外部物件來達到這一目的,此時的內部類機制可以自動照料它。在run()中,可看到對t 的訪問是直接進行的,似乎它是SeparateSubTask 的一個欄位。父類中的t 欄位現在可以變成private,因為SeparateSubTask 能在未獲任何特殊許可的前提下自由地訪問它——而且無論如何都該儘可能地把欄位變成“私有”屬性,以防來自類外的某種力量不慎地改變它們。無論在什麼時候,只要注意到類相互之間結合得比較緊密,就可考慮利用內部類來改善程式碼的編寫與維護。

5     用主類合併執行緒

在上面的例子中,我們看到執行緒類(Thread)與程式的主類(Main)是分隔開的。這樣做非常合理,而且易於理解。然而,還有另一種方式也是經常要用到的。儘管它不十分明確,但一般都要更簡潔一些(這也解釋了它為什麼十分流行)。通過將主程式類變成一個執行緒,這種形式可將主程式類與執行緒類合併到一起。由於對一個GUI 程式來說,主程式類必須從Frame 或Applet 繼承,所以必須用一個介面加入額外的功能。這個介面叫作Runnable ,其中包含了與Thread 一致的基本方法。事實上,Thread 也實現了Runnable ,它只指出有一個run()方法。

對合並後的程式/執行緒來說,它的用法不是十分明確。當我們啟動程式時,會建立一個Runnable(可執行的)物件,但不會自行啟動執行緒。執行緒的啟動必須明確進行。下面這個程式向我們演示了這一點,它再現了Counter2 的功能:

5.1     程式碼

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

 

public class Counter3 extends Applet implements Runnable {

    private intcount= 0;

    private booleanrunFlag= true;

    private Thread selfThread= null;

    private Button onOff= newButton("Toggle"),start= newButton("Start");

    private TextField t= newTextField(10);

 

    public voidinit() {

        add(t);

        start.addActionListener(new StartL());

        add(start);

        onOff.addActionListener(new OnOffL());

        add(onOff);

    }

 

    public voidrun() {

        while (true) {

            try {

                selfThread.sleep(100);

            } catch (InterruptedException e) {

            }

            if (runFlag)

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

        }

    }

 

    class StartL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            if (selfThread == null){

                selfThread = new Thread(Counter3.this);

                selfThread.start();

            }

        }

    }

 

    class OnOffL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            runFlag = !runFlag;

        }

    }

 

    public staticvoidmain(String[] args){

        Counter3 applet = new Counter3();

        Frame aFrame = new Frame("Counter3");

        aFrame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

 

5.2     執行

現在run()位於類內,但它在init()結束以後仍處在“睡眠”狀態。若按下啟動按鈕,執行緒便會用多少有些曖昧的表達方式建立(若執行緒尚不存在):

new Thread(Counter3.this);

若某樣東西有一個Runnable 介面,實際只是意味著它有一個run()方法,但不存在與之相關的任何特殊東西——它不具有任何天生的執行緒處理能力,這與那些從Thread 繼承的類是不同的。所以為了從一個Runnable 物件產生執行緒,必須單獨建立一個執行緒,併為其傳遞Runnable 物件;可為其使用一個特殊的構建器,並令其採用一個Runnable 作為自己的引數使用。隨後便可為那個執行緒呼叫start(),如下所示:

selfThread.start();

它的作用是執行常規初始化操作,然後呼叫run()。

Runnable 介面最大的一個優點是所有東西都從屬於相同的類。若需訪問什麼東西,只需簡單地訪問它即可,不需要涉及一個獨立的物件。但為這種便利也是要付出代價的——只可為那個特定的物件執行單獨一個執行緒(儘管可建立那種型別的多個物件,或者在不同的類裡建立其他物件)。

注意Runnable 介面本身並不是造成這一限制的罪魁禍首。它是由於Runnable 與我們的主類合併造成的,因為每個應用只能主類的一個物件。

6     製作多個執行緒

現在考慮一下建立多個不同的執行緒的問題。我們不可用前面的例子來做到這一點,所以必須倒退回去,利用從Thread 繼承的多個獨立類來封裝run()。但這是一種更常規的方案,而且更易理解,所以儘管前例揭示了我們經常都能看到的編碼樣式,但並不推薦在大多數情況下都那樣做,因為它只是稍微複雜一些,而且靈活性稍低一些。

下面這個例子用計數器和切換按鈕再現了前面的編碼樣式。但這一次,一個特定計數器的所有資訊(按鈕和文字欄位)都位於它自己的、從Thread 繼承的物件內。Ticker 中的所有欄位都具有private(私有)屬性,這意味著Ticker 的具體實現方案可根據實際情況任意修改,其中包括修改用於獲取和顯示資訊的資料元件的數量及型別。建立好一個Ticker 物件以後,構建器便請求一個AWT 容器(Container)的控制代碼——Ticker 用自己的可視元件填充那個容器。採用這種方式,以後一旦改變了可視元件,使用Ticker 的程式碼便不需要另行修改一道。

6.1     程式碼

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

 

class Ticker extends Thread {

    private Button b = new Button("Toggle");

    private TextField t= newTextField(10);

    private intcount= 0;

    private booleanrunFlag= true;

 

    public Ticker(Container c) {

        b.addActionListener(new ToggleL());

        Panel p = new Panel();

        p.add(t);

        p.add(b);

        c.add(p);

    }

 

    class ToggleL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            runFlag = !runFlag;

        }

    }

 

    public voidrun() {

        while (true) {

            if (runFlag)

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

            try {

                sleep(100);

            } catch (InterruptedException e) {

            }

        }

    }

}

 

public class Counter4 extends Applet {

    private Button start= newButton("Start");

    private booleanstarted= false;

    private Ticker[] s;

    private booleanisApplet= true;

    private intsize;

 

    public voidinit() {

        // Get parameter "size" from Web page:

        if (isApplet)

            size = Integer.parseInt(getParameter("size"));

        s = new Ticker[size];

        for (int i = 0; i < s.length; i++)

            s[i] = newTicker(this);

        start.addActionListener(new StartL());

        add(start);

    }

 

    class StartL implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            if (!started) {

                started = true;

                for (int i = 0; i < s.length; i++)

                    s[i].start();

            }

        }

    }

 

    public staticvoidmain(String[] args){

        Counter4 applet = new Counter4();

        // This isn't an applet, so set the flag and

        // produce the parameter values from args:

        applet.isApplet = false;

        applet.size = (args.length == 0 ? 5 : Integer.parseInt(args[0]));

        Frame aFrame = new Frame("Counter4");

        aFrame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(200, applet.size * 50);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

 

6.2     執行

Ticker 不僅包括了自己的執行緒處理機制,也提供了控制與顯示執行緒的工具。可按自己的意願建立任意數量的執行緒,毋需明確地建立視窗化元件。

在Counter4 中,有一個名為s 的Ticker 物件的陣列。為獲得最大的靈活性,這個陣列的長度是用程式片引數接觸Web 頁而初始化的。下面是網頁中長度引數大致的樣子,它們嵌於對程式片(applet)的描述內容

中:

<applet code=Counter4width=600 height=600>

<param name=sizevalue="20">

</applet>

其中,param,name和value 是所有Web 頁都適用的關鍵字。name 是指程式中對引數的一種引用稱謂,value可以是任何字串(並不僅僅是解析成一個數字的東西)。

我們注意到對陣列s 長度的判斷是在init()內部完成的,它沒有作為s 的內嵌定義的一部分提供。換言之,不可將下述程式碼作為類定義的一部分使用(應該位於任何方法的外部):

inst size =Integer.parseInt(getParameter("Size"));

Ticker[] s = new Ticker[size]

可把它編譯出來,但會在執行期得到一個空指標違例。但若將getParameter()初始化移入init(),則可正常工作。程式片框架會進行必要的啟動工作,以便在進入init()前收集好一些引數。

此外,上述程式碼被同時設定成一個程式片和一個應用(程式)。在它是應用程式的情況下,size 引數可從命令列裡提取出來(否則就提供一個預設的值)。

陣列的長度建好以後,就可以建立新的Ticker 物件;作為Ticker 構建器的一部分,用於每個Ticker 的按鈕和文字欄位就會加入程式片。

按下Start 按鈕後,會在整個Ticker 數組裡遍歷,併為每個Ticker 呼叫start()。記住,start()會進行必要的執行緒初始化工作,然後為那個執行緒呼叫run()。

ToggleL 監視器只是簡單地切換Ticker中的標記,一旦對應執行緒以後需要修改這個標記,它會作出相應的反應。

這個例子的一個好處是它使我們能夠方便地建立由單獨子任務構成的大型集合,並以監視它們的行為。在這種情況下,我們會發現隨著子任務數量的增多,機器顯示出來的數字可能會出現更大的分歧,這是由於為執行緒提供服務的方式造成的。

亦可試著體驗一下sleep(100)在Ticker.run()中的重要作用。若刪除sleep(),那麼在按下一個切換按鈕前,情況仍然會進展良好。按下按鈕以後,那個特定的執行緒就會出現一個失敗的runFlag,而且run()會深深地陷入一個無限迴圈——很難在多工處理期間中止退出。因此,程式對使用者操作的反應靈敏度會大幅度降低。

7   D a e m o n 執行緒

“Daemon”執行緒的作用是在程式的執行期間於後臺提供一種“常規”服務,但它並不屬於程式的一個基本部分。因此,一旦所有非Daemon 執行緒完成,程式也會中止執行。相反,假若有任何非Daemon 執行緒仍在執行(比如還有一個正在執行main()的執行緒),則程式的執行不會中止。

通過呼叫isDaemon(),可調查一個執行緒是不是一個Daemon,而且能用setDaemon()開啟或者關閉一個執行緒的Daemon 狀態。如果是一個Daemon 執行緒,那麼它建立的任何執行緒也會自動具備Daemon 屬性。

下面這個例子演示了Daemon 執行緒的用法:

7.1     程式碼

import java.io.*;

 

class Daemon extends Thread {

    private staticfinalintSIZE= 10;

    private Thread[] t= newThread[SIZE];

 

    public Daemon() {

        setDaemon(true);

        start();

    }

 

    public voidrun() {

        for (int i = 0; i < SIZE; i++)

            t[i] = newDaemonSpawn(i);

        for (int i = 0; i < SIZE; i++)

            System.out.println("t[" + i + "].isDaemon() = "+ t[i].isDaemon());

        while (true)

            yield();

    }

}

 

class DaemonSpawn extends Thread {

    public DaemonSpawn(int i) {

        System.out.println("DaemonSpawn " + i + " started");

        start();

    }

 

    public voidrun() {

        while (true)

            yield();

    }

}

 

public class Daemons {

    public staticvoidmain(String[] args){

        Thread d = new Daemon();

        System.out.println("d.isDaemon() = " + d.isDaemon());

        // Allow the daemon threads to f