粒子
类Applet定义了一个用于移动物体的完全假想的模型。每个粒子(particle)用它的(x,y)位置表示。同时,每个粒子包括了一个用来随意移动它自身位置的方法和一个可以通过给定的java.awt.Graphics对象来绘制其自身(一个小方块)的方法。
虽然particle对象内部并不包含任何并发操作,但是他的方法可能被多个并发行为调用。当一个操作正在执行一个move操作而几乎同时另一个操作正在调用draw方法时,我们需要保证draw方法所描绘的是particle所在的准确位置。这里,我们要求draw方法可以在move方法被调用之前或者是之后使用粒子的当前位置进行绘制。举例而言,当draw操作使用move方法调用前的y值和move方法调用后的x值绘制一个例子的图形,就会产生一个概念性的错误。如果我们允许这种情况出现,那draw方法就可能吧一个粒子绘制在其并没有出现过的位置上。
我们可以通过使用synchornized 关键字来防止这种情况的发生,synchronized可以修饰一个方法或者是一个代码块。object的每一个实例都会在进入一个同步方法前加锁,并在离开这个方法时自动的释放掉这把锁。对于代码块的操作也是一样,只是同步代码块需要一个参数致命对那一个对象加锁。最常用的参数是this,它意味着锁住当前正在执行的方法所属的对象。当一个线程持有了一把锁后,其他线程必须阻塞,等待着线程适当掉这把锁。加锁对于非同步的方法不起作用,因此即便另一个线程有这个锁,这个非同步方法也可以执行。
import java.awt.Graphics;
import java.util.Random;
public class Particle {
protected int x;
protected int y;
protected final Random rng=new Random();
public Particle(int initialX,int initialY){
x=initialX;
y=initialY;
}
public synchronized void move(){
x+=rng.nextInt(10)-5;
y+=rng.nextInt(10)-10;
}
@SuppressWarnings("unused")
public void draw(Graphics g){
int lx,ly;
synchronized (this) {lx=x;ly=y;}
g.drawRect(lx, lx, 10,10);
}
}
以上代码,我们使用final关键字修饰随机数生成器rng,这样做的目的是为了确保这个成员变量的引用不会被改变。这样一来,它就不会被我们的加锁规则所影响。许多并发程序中都大量地使用了final关键字,以帮助将减少同步需要的设计意图自动在文档中突出说明。
draw方法需要同时得到一对一致的x和y值。由于一个方法一次只能返回一个值,但是我们在这里却需要同时获得x和y值,因此不能简单地把变量的访问封装在一个同步的方法中。相反,我们使用同一个代码同步块代替。
可以看到draw方法遵守了我们上面提到的那个重要的规则,在调用其他对象上的方式时释放掉了锁(这个方法就是g.drawRect)。而move方法看似违背了我们的规则,它调用了rng.nextInt方法。然而在这里,我们这样做是有它的原因的,因为每一个particle都包含其自身的rng对象,因而rng对象是particle的一部分,所以它应该不能被看成规则中所描述的“其他对象”。
ParticleCanvas
particleCanvas是java.awt.Canvas的一个简单自雷,它为所有粒子提供一个绘制区域。它的主要功能职责是,当其paint方法被调用时,调用所有粒子的draw方法。
但是,ParticleCanvas本身并不负责创建和管理这些粒子。ParticleCanvas可以被动或者主动的得到这些粒子对象,这里我选择被动的方式。
实例变量particles是一个数组,保存了已存在的particle对象。这个变量在需要的时候由Applet负责设置,被paint方法使用。我们同样应用了默认的规则,使用了两个简单的、同步的get和set方法来访问particles变量,避免了直接访问particles本身。为了简化程序使其被正确地使用,这个particles变量不允许被设为null,而是被初始化为一个空数组。
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
public class ParticleCanvas extends Canvas {
private static final long serialVersionUID = 1L;
private Particle[] particles=new Particle[0];
public ParticleCanvas(int size) {
setSize(new Dimension(size,size));
}
//将被applet调用
protected synchronized void setParticles(Particle[] ps) {
if(ps==null)
throw new IllegalArgumentException("CannotProceed set null!");
particles=ps;
}
protected Particle[] getParticles() {
return particles;
}
/**
* 重写Canvas里面的paint
*/
public void paint(Graphics g){
Particle[] ps=getParticles();
for(int i=0;i<ps.length;i++)
ps[i].draw(g);
}
}
ParticleApplet
类Particle和ParticleCanvas可以作为几个其他的程序的基础使用。但是对于particleApplet而言,我们要所要做的是:使每一个例子以一种自治的“持续”的方式运动,并且更新显示这些例子的位置。
每一个Thread的实例维护了执行和管理那些组成其动作的调用序列所需要的控制状态,Thread类的最常用的构造函数接受一个Runnable对象作为它的参数,在线程被启动时会调用这个Runnable对象的run方法。由于人和类都可以实现Runnable接口,因此通常一种很方便且使用的做法就是把一个Runnable作为一个匿名内部类实现。
import java.applet.Applet;
public class ParticleApplet extends Applet{
/**
*
*/
private static final long serialVersionUID = 1L;
protected Thread[] threads=null;//不运行的时候默认值
protected final ParticleCanvas canvas =new ParticleCanvas(100);
public void init(){add(canvas);}
protected Thread makeThread(final Particle p) {
Runnable runloop=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for(;;){
p.move();
canvas.repaint();
Thread.sleep(100);
}
} catch (InterruptedException e) {
// TODO: handle exception
return ;
}
}
};
return new Thread(runloop);
}
public synchronized void start(){
int n=10;//
if(threads==null){
Particle[] particles=new Particle[n];
for(int i=0;i<n;++i)
particles[i]=new Particle(50, 50);
canvas.setParticles(particles);
threads=new Thread[n];
for(int i=0;i<n;++i){
threads[i]=makeThread(particles[i]);
threads[i].start();
}
}
}
public synchronized void stop(){
if(threads!=null){
for(int i=0;i<threads.length;i++)
threads[i].interrupt();
threads=null;
}
}
}