编写一个多线程程序的实例,它模拟8只鸟在一个笼子里移动的情形。每只“鸟”是一个扩展Thread的类,它负责控制自身的移动。这个“笼子”是扩展Frame的类,它包含有3个按钮,用于启动、终止“鸟”和退出程序。“鸟”在碰到“笼子”的边缘时应返回来,这样就不会离开“笼子”。“鸟”在初始化时,可随机放置在“笼子”的任何地方。这里首先从“鸟”类开始,可按如下步骤去实现它。
(1)这个类扩展Thread类,这样就可独立执行自己的线程。
(2)按上述线程设计的技巧,使得“鸟”类可在任何时刻停止执行。
(3)设置两个域x、y,作为“鸟”的当前坐标。
(4)为了重新移动“鸟”到一个新的x、y坐标,要在方法中重新计算它的坐标,使它产生移动效果。
(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值来控制这个循环。在构造方法中,将“鸟”的位置坐标的初始值传递给一个对象;“鸟”碰到“笼子”边缘返回也可以使用一种直接的方式:直接改变“鸟”的坐标的当前值。于是,如果检查“鸟”超出了“笼子”的范围,则要使“鸟”朝相反方向运动,也就是要改变相应坐标增量的符号。
这里还使用了一点小技巧,就是随机选择xdir和ydir的初始值,该值可以为2或-2。这是因为以下几点。
· Math.random( )返回0.0~1.0之间的一个实数。
· Math.round(Math.random( ))返回0或1。
· 2*(int)Math.round(Math.random( ))返回0或2。
· 1 - 2*(int)Math.round(Math.random( ))返回-1或1。
· 2*(1 - 2*(int)Math.round(Math.random( )))返回-2或2。
下面再来分析如何实现Cage类。
· 该类需要Quit、Start和Stop这3个按钮域,此外,需要使用一个数组存贮8只“鸟”的引用。
· 在构造方法中进行布局设计,使得Frame为300*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( )方法也非常简单,只是在当前的(x,y)坐标下画出一个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( )方法。
(3)repaint( )方法的调用导致了图像更新,从而产生Bird移动的感觉。
最后的效果如图9-3所示。
图9-3 在一个Cage内移动的Bird,每只Bird都是一个独立执行的线程