前面我们利用多线程实现了一个多个小球运动的程序,但是我们知道每个操作系统所能开辟的线程数量是有限的,并且随着线程的增多,程序的运行性能会逐渐下降。因此,我们就想着把它转变为只有一个主线程和一个子线程的程序。
一、线程的理解
1.线程,是在进程中独立运行的子任务。为什么需要多线程编程呢?举个简单的例子,比如坦克大战的游戏。但游戏开始时,我们要求画面上要不断地生成新的飞机,但同时我们又要控制自己的飞机进行移动。如果我们只有一个主线程,那么一旦这个主线程在死循环执行生成新飞机的代码时,我们是没办法执行其他代码的,也就是我们没法控制自己的飞机。因此我们必须额外再开辟一个线程来控制我们自己的飞机。这就是线程的作用之一,它可以让我们的程序同时进行多个操作。
2.每一个运行的程序都会有自己的线程,有且只有一个。这个线程我们称之为主线程。这个主线程会依次执行我们的整个代码。而我们用start()方法开辟的新线程,它运行的是run()方法的代码。这个run()方法的代码会和我们的主线程同步进行(表面上看起来是同时进行的)。
3.线程结束后不能重新启动。
4.如果没有调用start()方法来启动线程,而是直接调用run(),那么这时run()只是一个普通方法,它需要在主线程中排队执行。
5.子线程的存在时间取决于run()方法。如果run()方法是一个死循环,那么这个线程就会一直存在,直到主线程停止。如果run()方法只是一个简单的运行代码,不是死循环,那么当run()方法执行完毕,这个子线程就会被关闭。
二、多个小球运动的基本思路
1.多线程
前面我们实现的多个小球运动是采用多线程来实现的。基本思路就是,为每一个小球开辟一个独立的线程,我们让小球类Ball去继承Thread线程类,并重写run()方法。每一次点击界面的时候,生成一个新的球类对象,并且为这个对象开辟一个线程。
2.单线程
为所有的小球开辟一个共用的子进程,这个子进程会不断地生成新的小球。因此我们让界面类BallFrame去继承Thread线程类,并重写run()方法。在这个run方法中我们通过一个循环来调用所有的小球对象,并让这些小球对象去执行move方法,实现所有的小球一同运动的效果。
三、详细解析
单线程多小球的基本结构
四、具体代码
//实现球运动的界面类BallFrame
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class BallFrame extends JFrame implements Runnable{
public Ball ball;
public Random random=new Random();
ArrayList<Ball>list=new ArrayList();
static public void main(String[] args) {
BallFrame jf=new BallFrame();
jf.initUI();
Thread frameThread=new Thread(jf);
frameThread.start();
}
public void initUI() {
//初始化界面
this.setTitle("小球运动");
this.setSize(800, 600);
this.setDefaultCloseOperation(3);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setVisible(true);
//在线程启动之前先增加一个小球对象
ball=new Ball(this,100);
list.add(ball);
}
//重写重绘方法
public void paint(Graphics g) {
//调用父类的重绘方法
super.paint(g);
for(int i=1;i<list.size();i++) {
g.setColor(list.get(i).color);
g.fillOval(list.get(i).x, list.get(i).y, list.get(i).size, list.get(i).size);
}
}
public void run() {
int controlnum=0;//控制小球的生成速度
while(true) {
for(int i=0;i<list.size();i++) {
list.get(i).move();
list.get(i).speed();
repaint();
list.get(i).collide(i);
if(list.get(i).blood<=0) list.remove(i);
}
if(controlnum==100) {
//生成新的小球
int randomX=random.nextInt(600);
Color color=new Color(random.nextInt(256));
int speednumber=random.nextInt(5);
Ball ball=new Ball(randomX,0,random.nextInt(20)+20,color,speednumber-5,speednumber,this,100);
list.add(ball);
controlnum=0;
}
else controlnum++;
//休眠10ms,也就是每隔10ms小球会移动一次
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
import java.awt.Color;
import java.awt.Graphics;
public class Ball {
public int x=0;//起始位置的x
public int y=50;//起始位置的y
public int size=30;//小球的大小
public Color color=Color.black;//小球的颜色
public Graphics g;//画笔
public int speedx=3;//x的改变速度
public int speedy=3;//y的改变速度
public BallFrame bf;
public int blood;//小球的血量
//构造函数
public Ball(BallFrame bf,int blood) {
this.bf=bf;
this.blood=blood;
}
//构造函数
public Ball(int x,int y,int size,Color color,int speedx,int speedy,BallFrame bf,int blood) {
this.x=x;
this.y=y;
this.size=size;
this.color=color;
this.speedx=speedx;
this.speedy=speedy;
this.bf=bf;
this.blood=blood;
}
//控制小球的移动速度
public void speed() {
if(x+size>bf.getWidth()) {
speedx=-Math.abs(speedx);
blood-=10;
}
else if(x<0) {
speedx=Math.abs(speedx);
blood-=10;
}
if(y+size>bf.getHeight()) {
speedy=-Math.abs(speedy);
blood-=10;
}
else if(y<0) {
speedy=Math.abs(speedy);
blood-=10;
}
}
//小球移动
public void move() {
y+=speedy;
x+=speedx;
}
//判断小球之间是否发生碰撞
public void collide(int i) {
//判断是否发生了碰撞
for(int j=0;j<bf.list.size();j++) {
if(i==j) continue;//自己和自己不碰撞
int DistanceX=x-bf.list.get(j).x;
int DistanceY=y-bf.list.get(j).y;
int RadiiI=size/2;
int RadiiJ=bf.list.get(j).size/2;
//两个小球之间的距离小于两者的半径和
if((DistanceX*DistanceX+DistanceY*DistanceY)<=((RadiiI+RadiiJ)*(RadiiI+RadiiJ))) {
if(x>bf.list.get(j).x) { //如果自己的x坐标大于发生碰撞的小球的x坐标,由数学知识可知应该朝正向运动
speedx=Math.abs(speedx);
}
else //如果自己的x坐标小于发生碰撞的小球的x坐标,由数学知识可知应该朝负向运动
{
speedx=-Math.abs(speedx);
}
if(y>bf.list.get(j).y) { //如果自己的x坐标大于发生碰撞的小球的x坐标,由数学知识可知应该朝正向运动
speedy=Math.abs(speedy);
}
else //如果自己的x坐标小于发生碰撞的小球的x坐标,由数学知识可知应该朝负向运动
{
speedy=-Math.abs(speedy);
}
blood-=10;//发生碰撞,血量减少
}
}
}
}
五、反思总结
1.小球的移动速度不能过快,否则会连城一条线。可以重新定义一个controlnum来控制小球的移动速度,小球的移速是10ms移动一次,生成的话可以定义为1000ms。也就是,每移动100次再生成一个小球。
2.线程的启动必须在界面可见之后。