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 finish

        // their startup processes:

        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("Waiting for CR");

        try {

            stdin.readLine();

        } catch (IOException e) {

        }

    }

} /// :~

7.2     执行

Daemon 线程可将自己的Daemon 标记设置成“真”,然后产生一系列其他线程,而且认为它们也具有Daemon属性。随后,它进入一个无限循环,在其中调用yield(),放弃对其他进程的控制。在这个程序早期的一个版本中,无限循环会使int 计数器增值,但会使整个程序都好象陷入停顿状态。换用yield()后,却可使程序充满“活力”,不会使人产生停滞或反应迟钝的感觉。一旦main()完成自己的工作,便没有什么能阻止程序中断运行,因为这里运行的只有Daemon 线程。所以能看到启动所有Daemon 线程后显示出来的结果,System.in 也进行了相应的设置,使程序中断前能等待一个回车。如果不进行这样的设置,就只能看到创建Daemon 线程的一部分结果(试试将readLine()代码换成不同长度的sleep()调用,看看会有什么表现)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值