线程的生命周期
试了一下代码应该看到了动画了吧,小球确实是动起来了。只是和想象的有所差别,小球飞的实在是太快了。现在想法就是怎么样才能使小球优雅的,至少是以可控制的速度飞行。我们的思路是,能不能每次在y++以后,让线程等待一会儿,那就再重画。其实有一条指令是可以让线程休眠的,这是一个类方法——Thread.sleep()。
try{
Thread.sleep(30);
}catch(Exception e){}
异常处理的写法是,try后面跟的大括弧保护了有可能出现异常的代码,我们这里写的线程休眠30毫秒。catch后面的大括弧里面放的是万一出现了异常怎么处理,catch后面跟的小括弧很像是方法的参数,这还少有人给的内容,内容放在e里面;这就是系统的内容,放的是发生了什么事情。这个例子中,我们不做任何处理,睡死就睡死好了,无所谓的事情,所以catch后面的大括弧就这么放着,什么内容都没有,这种事情常常会发生,但是你不能写try catch。
import java.awt.*;
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
Thread t = new Thread(mp);
t.start();
w.show();
}
}
class MyOval extends Panel implements Runnable{
int x=30,y=30;
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
y++;
if(y>400){
y=0;
}
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
}
继续上一次的小球代码加上线程休眠的指令,小球是不是慢慢动起来了,可能有些人的操作系统中的小球会闪的很厉害,这个问题暂时先别管,后续我会跟你们一起解决这个问题。
现在小球可以慢慢的垂直落下,能不能让这个小球斜着飞,这个不难,那么还有没有办法让小球弹回来呢?
代码自己写过几遍之后,我借此讨论一下线程的生命周期。
可以理解在new Thread()后,我们就在内存里得到了一个进程对象,这样线程对象和其它对象没有区别,安静地待在内存里,我们称呼这种状态叫做新建状态。
当调用了start方法之后,线程就启动了,但是这并不意味着run方法中的代码会立刻被执行,start方法也就是通知系统,自己做好了准备,可以被轮转了,所以我们称这种状态为就绪状态。
这里要注意的是,由于run是一个正常方法,也是可以被调用的,但是你不能直接调用run方法,这么做线程机制就不存在了,run方法就成了一个普通的方法了,让线程处于就绪状态的只有start方法。
至于线程什么时候进入运行状态,都不是我们程序员能够控制的了,这取决于线程调度机制的调度。但是你能理解,线程不能一直处于运行状态,当CPU轮转到其他线程时,线程将回到就绪状态。
在这个例子中,使用了sleep,在sleep起作用的时候,线程处于阻塞状态,还有其他的阻塞语句会让线程处于阻塞状态,当线程处于阻塞状态时,CPU不会轮转到这个线程上。
在run方法执行结束后,线程就处于死亡状态。
这样我们知道线程会有创建,就绪,运行,阻塞,死亡5种状态。
另外,线程还有优先级,优先级高的线程将获得更多的执行机会,最小的优先级1,最大的优先级是10,普通优先级是5,线程的默认优先级和创建它的父线程相同,在我们的例子中父线程是main线程,main线程具有普通优先级5,可以通过t.setPriority(6)来改变优先级。
问题是,不同的操作系统对线程的优先级支持是不同的,所以要尽量避免直接设置优先级数字,而是使用Thread.MAX_PRIORITY,Thread.MIN_PRIORITY,Thread.NORM_PRIORITY这样的常量来设置,JVM会协调操作系统。
## 小球碰撞 ##
不知道小球遇到墙反弹你的想法是什么,我们还是一样现将任务分解:
1.做个窗体
2.做个小球
3.让小球斜着飞
4.遇到墙反弹
罗列出来以后发现好像也就第四步我们没有讲解过,那么我们可以从斜着飞的代码开始。
class MyOval extends Panel implements Runnable{
int x=30,y=30;
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
x++;
y++;
if(y>400){
y=0;
}
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
我们先得到了一个能够向右下角斜着飞的小球,下一步要判断墙在哪儿。之前我们已经用代码判断出了下面的墙,下一步就是见到墙以后,小球应该反弹回来,一个向右下飞行的小球遇到右边的墙。应该向左下飞行,如果做到,x++,y–.
class MyOval extends Panel implements Runnable{
int x=30,y=30;
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
x++;
y++;
if(y>300){
x--;
y++;
}
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
}
不过从结果上看小球没有反弹,而是消失在右边的墙体里了,我们来分析一下为什么会这样。我们判断墙的代码是if(x>300){},问题是,小球的坐标是左上角的坐标,如果用x和300比较,必须是整个小球小球都进入了右边的墙角里,才能够符合条件,加上设置窗体的大小为300*400,这个值指的是窗体整体的大小,窗体里面的白色区域要比这个值小,白色区域的大小差不多是283,361(其实我无法保证你的电脑也是这个值,还是要自己去试一下),我们看到横向的窗体边框占了17个像素点,纵向上占了39个像素点,因为窗体上面有一个放标题的蓝色区域,我们修正以后,判断墙的值就变成了if(x<263){},小球的直径一并去掉。
墙是个问题,但是真正的问题是,小球咋就飞回来呢?你别光看着我这样磨叽,别让我一直带你们飞,最好还是自己找到解决办法,我们看到小球在墙的边缘时,就是x=263,当然y=263,代码遇到x++,y++以后,x=264,y=264,程序循环回来,是不是符合if的条件了,现在x–,y++,这样x=263,y=265。程序又循环回来,又遇到了x++,y++,x=264,y=266,再次符合条件,x的值又被减去1.看出来什么问题了吗?虽然x–了,但是立刻又被++回去,结果就是x的值一直不变,y以两倍的速度变大,小球贴着墙边滑下去了。也就是说我们减x的值得时候,就不要再加了。
class MyOval extends Panel implements Runnable{
int x=30,y=30;
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
if(x>263){
x++;
y--;
}
else {
x++;
y++;
}
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
}
我从未讲过if循环语句,我认为不需要,你完全可以根据遇见的结果去认识这个if语句,很多知识都可以被猜出来,学习能力强的马上就能理解。
试一下,好像小球还是没有反弹,还是来思考一下,问题到底出在哪里?怎么去解决?我们拿墙做判断条件,当小球反弹后,便离开墙,一旦离开墙,反弹的条件就不满足了,小球又会恢复右下方飞行,能不能遇到墙,改变了飞行姿态后,就固定下来这个飞行姿态,而和墙没有关系。在程序设计过程中,我们常常在逻辑遇到困难的时候,想到通过增加变量来解决问题,根据上面的描述,我们增加一个变量来解决问题,根据上面的描述,我们增加一个变量来存储当前的飞行姿态,并且实先这样来
class MyOval extends Panel implements Runnable{
int x=30,y=30;
int att =0;//0有下,1左下,2左上,3右上
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
if(att==0){
x++;
y--;
}
if(att==1) {
x--;
y++;
}
if(att==2){
x--;
y--;
}
if(att==3){
x++;
y--;
}
//改变飞行姿态
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
}
这段代码使用了注释,体会一下如果没有注释,要不了多久就会忘了开始的定义,思路就会乱掉,其中改变飞行姿态的代码还没有提供,我想目前的代码还是可以理解的。
我们再来看改变飞行姿态的代码,并不是简单的0变成1,1变成2,2变成3,3变成0这么简单,,每面墙有两个可能飞来的方向,也有两个反弹出去的方向,所以反弹代码要对飞来的方向进行判断。下面是改变飞行方向的代码
//改变飞行方向
//改变飞行姿态
if(x>263){
if(att==0){
att=1;
}else{
att=2;
}
}
if(y>341){
if(att==1){
att=2;
}else{
att=3;
}
}
if(x<0){
if(att==2){
att=3;
}else{
att=0;
}
}
if(y<0){
if(att==3){
att=0;
}else{
att=1;
}
}
将上述代码结合就能看家年最后的效果了
我把代码整合写一遍
import java.awt.*;
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
Thread t = new Thread(mp);
t.start();
w.show();
}
}
class MyOval extends Panel implements Runnable{
int x=30,y=30;
int att=0; //0右下,1左下,2左上,3右上
public void paint(Graphics g){
g.fillOval(x, y, 20, 20);
}
public void run(){
while(true){
//定义飞行姿态
if(att==0){
x++;
y++;
}
if(att==1){
x--;
y++;
}
if(att==2){
x--;
y--;
}
if(att==3){
x++;
y--;
}
//改变飞行姿态
if(x>263){
if(att==0){
att=1;
}else{
att=2;
}
}
if(y>341){
if(att==1){
att=2;
}else{
att=3;
}
}
if(x<0){
if(att==2){
att=3;
}else{
att=0;
}
}
if(y<0){
if(att==3){
att=0;
}else{
att=1;
}
}
try{
Thread.sleep(30);
}catch(Exception e){}
repaint();
}
}
}
今天的就更新到这里,下期再见