您的位置: 网站首页 > 程序开发 > Java程序设计 > 第9章 多线程 > 【9.7 上 机 实 验】

9.7 上 机 实 验

 

9.7 

编写一个多线程程序的实例,它模拟8只鸟在一个笼子里移动的情形。每只“鸟”是一个扩展Thread的类,它负责控制自身的移动。这个“笼子”是扩展Frame的类,它包含有3个按钮,用于启动、终止“鸟”和退出程序。“鸟”在碰到“笼子”的边缘时应返回来,这样就不会离开“笼子”。“鸟”在初始化时,可随机放置在“笼子”的任何地方。这里首先从“鸟”类开始,可按如下步骤去实现它。

1)这个类扩展Thread类,这样就可独立执行自己的线程。

2)按上述线程设计的技巧,使得“鸟”类可在任何时刻停止执行。

3)设置两个域xy,作为“鸟”的当前坐标。

4)为了重新移动“鸟”到一个新的xy坐标,要在方法中重新计算它的坐标,使它产生移动效果。

5)由于有多个移动的“鸟”,所以在每个线程的run( )方法中应该调用sleep( )方法,这样就可以让出时间使操作系统去移动其他的“鸟”。

6)为了保证“鸟”在碰到“笼子”的边缘时返回来,则需要知道“笼子”的大小,因而要在构造方法中将一个Cage对象作为参数,以便把“笼子”的大小传递过来。

下面是“鸟”这个类的代码。

/** Bird.java @author ZDS

 * 2007-2-19 下午22:38:15 */

packagechap09;

importjava.awt.Graphics;

importjava.awt.Image;

importjava.awt.Toolkit;

importjava.net.URL;

/** 定义一个小鸟对象,继承于Thread对象*/

public class Bird extends Thread {

    private int xdir = 2 * (1 - 2 * (int) Math.round(Math.random()));

    private int ydir = 2 * (1 - 2 * (int) Math.round(Math.random()));

    private boolean running = false;

    private Cage cage = null;

    protected int x, y;

    Image bird = null;  // 小鸟图片

    public Bird(Cage _cage, int _x, int _y) {

        cage = _cage;

        URL url = cage.getClass().getResource("/chap09/bird.gif");

        bird = Toolkit.getDefaultToolkit().getImage(url);

        x = _x;

        y = _y;

        start();

    }

    public void start() {

        running = true;

        super.start();

    }

    public void halt() {

        running = false;

    }

    public void run() {

        while (running) {

            move();

            try {

                sleep(50);

            } catch (InterruptedException ie) {

                System.err.println("Thread interrupted");

            }

            cage.repaint();

        }

    }

    // 移动小鸟

    private void move() {

        x += xdir;

        y += ydir;

        if (x > cage.getSize().width) {

            x = cage.getSize().width;

            xdir *= (-1);

        }

        if (x < 0)

            xdir *= (-1);

        if (y > cage.getSize().height) {

            y = cage.getSize().height;

            ydir *= (-1);

        }

        if (y < 0)

            ydir *= (-1);

    }

    // 绘制小鸟对象

    public void draw(Graphics g) {

        g.drawImage(bird, x, y, 50, 37, null, cage);

    }

}

按照前面所描述的线程的使用技巧,在run( )方法中应包含一个while循环,可以通过halt( )方法改变running变量为false值来控制这个循环。在构造方法中,将“鸟”的位置坐标的初始值传递给一个对象;“鸟”碰到“笼子”边缘返回也可以使用一种直接的方式:直接改变“鸟”的坐标的当前值。于是,如果检查“鸟”超出了“笼子”的范围,则要使“鸟”朝相反方向运动,也就是要改变相应坐标增量的符号。

这里还使用了一点小技巧,就是随机选择xdirydir的初始值,该值可以为2-2。这是因为以下几点。

·    Math.random( )返回0.0~1.0之间的一个实数。

·    Math.round(Math.random( ))返回01

·    2*(int)Math.round(Math.random( ))返回02

·    1 - 2*(int)Math.round(Math.random( ))返回-11

·    2*(1 - 2*(int)Math.round(Math.random( )))返回-22

下面再来分析如何实现Cage类。

·    该类需要QuitStartStop3个按钮域,此外,需要使用一个数组存贮8只“鸟”的引用。

·    在构造方法中进行布局设计,使得Frame300*300像素并且可改大小的可见窗口,然后随机初始化“鸟”所处的位置。

·    actionPerformed( )方法中进行监测,当相应的按钮被单击后,循环调用“鸟”数组的start( )halt( )方法。

如果Start按钮被单击,则在初始化新“鸟”之前,必须保证所有当前线程通过调用自身的halt( )方法而自行结束。特别是当初始化一组新“鸟”时,使用的是每只“鸟”当前位置的坐标,这样即使是初始化一组全新的“鸟”,但看起来就像每只“鸟”在它停止的位置上又恢复运动。

/** Cage.java @author ZDS

 * 2007-2-19 下午22:38:15 */

packagechap09;

importjava.awt.Button;

importjava.awt.FlowLayout;

importjava.awt.Graphics;

importjava.awt.event.ActionEvent;

importjava.awt.event.ActionListener;

importjavax.swing.JFrame;

/** 定义一个笼子对象,用来放置小鸟*/

publicclass Cage extends JFrame implements ActionListener {

    private Button quit = new Button("退出");

    private Button start = new Button("开始");

    private Button stop = new Button("停止");

    private Bird birds[] = new Bird[8];

publicCage() {

        super("Cage with Birds");

        setLayout(new FlowLayout());

        add(quit);

        quit.addActionListener(this);

        add(start);

        start.addActionListener(this);

        add(stop);

        stop.addActionListener(this);

        validate();

        setSize(300, 300);

        setVisible(true);

        // 创建指定数目的小鸟

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

            int x = (int) (getSize().width * Math.random());

            int y = (int) (getSize().height * Math.random());

            birds[i] = new Bird(this, x, y);

        }

    }

    // 执行退出、开始、停止命令

    public void actionPerformed(ActionEvent ae) {

        if (ae.getSource() == stop)

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

                birds[i].halt();

        if (ae.getSource() == start)

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

                birds[i].halt();

                birds[i] = new Bird(this, birds[i].x, birds[i].y);

            }

        if (ae.getSource() == quit)

            System.exit(0);

    }

    // 绘制所有的小鸟

    public void paint(Graphics g) {

        super.paint(g);

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

            if (birds[i] != null)

                birds[i].draw(g);

    }

    public static void main(String args[]) {

        Cage table = new Cage();

        table.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        table.setVisible(true);

    }

}

下面需要解决这些“鸟”在屏幕上如何显示的问题,每只“鸟”应当独立显示自身,但是没有一个图形对象可供绘图。这里的解决方案是从Cage类的paint( )方法中传递一个图形对象到每个“鸟”类的某个适当方法中,在那里画出“鸟”。因此,Cage类的paint( )方法如下。

public void paint(Graphics g) {

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

        if (birds[i] != null)

            birds[i].draw(g);

}

Bird类中相应的draw( )方法也非常简单,只是在当前的(xy)坐标下画出一个Image对象,因而在Bird类中要加入下述方法。

public void draw(Graphics g) {

    g.drawImage(bird, x, y, 30, 40, cage);

}

但是这个程序并不能很好地工作。虽然这些“鸟”都可以独立地运行,并且它们的run( )方法在启动后会不断地改变“鸟”的坐标,然而,真正的完成绘图的方法是Cage类的paint( )方法,而这个方法仅在Frame需要更新的时候才被调用。于是,要保证“鸟”在改变坐标时,在Bird类的run( )方法中调用Cage类的repaint( )方法,因此一个修改了的新方法包含了下面程序的最后一行。

public void run(){

    while (true) {

        move();

        try{

            sleep(120);

        }catch(InterruptedException ie) {

            System.err.println("Thread interrupted");

        }

        cage.repaint();

    }

}

这个类的执行过程如下。

1)当单击“开始”按钮后,每个Bird开始执行它自己的run( )方法。

2)每个Bird类的run( )方法不停地计算它的新坐标,休眠120毫秒后,再调用Cage类的repaint( )方法。

3repaint( )方法的调用导致了图像更新,从而产生Bird移动的感觉。

最后的效果如图9-3所示。

9-3  在一个Cage内移动的Bird,每只Bird都是一个独立执行的线程