>  기사  >  Java  >  Java에서 타이머를 분석하고 타이머를 사용하여 핀볼 게임을 만듭니다.

Java에서 타이머를 분석하고 타이머를 사용하여 핀볼 게임을 만듭니다.

高洛峰
高洛峰원래의
2016-12-16 13:31:001378검색

프로그래밍 과정에서 복잡한 제어 없이 간단한 예약 작업을 수행해야 하는 경우 JDK의 타이머 예약 작업을 사용하여 구현하는 것을 고려할 수 있습니다. 아래에서 LZ는 원리, 예시, 타이머 결함이라는 세 가지 측면에서 Java 타이머 타이머를 분석합니다.

1. 소개
Java에서는 Timer와 TimerTask라는 두 가지 클래스로 전체 예약 작업을 완료해야 합니다. 타이머는 스레드가 향후 백그라운드 스레드에서 실행될 작업을 예약하는 데 사용하는 도구인 타이머와 같은 API에 정의되어 있습니다. 작업은 한 번 실행되거나 주기적으로 반복되도록 예약할 수 있습니다. TimerTask별: 일회성 또는 반복 실행을 위해 타이머에 의해 예약된 작업입니다. Timer는 백그라운드 스레드에서 지정된 작업을 계획하고 실행하는 데 사용되는 타이머 도구인 반면 TimerTask는 추상 클래스이며 해당 하위 클래스는 Timer에 의해 예약될 수 있는 작업을 나타냅니다.
Timer 클래스
Timer 도구 클래스에는 네 가지 구성 메서드가 제공됩니다. 각 구성 메서드는 동시에 타이머 스레드를 시작합니다. 동시에 Timer 클래스는 외부 동기화 없이 여러 스레드가 단일 Timer 개체를 공유할 수 있도록 보장합니다. . 이므로 Timer 클래스는 스레드로부터 안전합니다. 그러나 각 Timer 개체는 모든 타이머 작업을 순차적으로 실행하는 데 사용되는 단일 백그라운드 스레드에 해당하므로 일반적으로 스레드 작업 실행에 소요되는 시간은 매우 짧지만 특수한 상황으로 인해 타이머 작업 실행 시간이 너무 길면 타이머의 작업 실행 스레드를 "독점적으로" 점유하고 모든 후속 스레드는 실행이 완료될 때까지 기다려야 합니다. 이로 인해 후속 작업의 실행이 지연되고 특정 상황에서 이러한 작업이 쌓이게 됩니다. 나중에 분석하세요.
프로그램이 타이머를 초기화하면 우리가 설정한 시간에 따라 예약된 작업이 실행됩니다. 타이머는 다음과 같이 다양한 상황에 적응할 수 있는 여러 가지 오버로딩 방법을 제공하는 스케줄 방법을 제공합니다. , 날짜 시간): 지정된 시간에 지정된 작업이 실행되도록 예약합니다.
일정(TimerTask task, Date firstTime, long period): 지정된 시간에 반복 고정 지연 실행을 시작하도록 지정된 작업을 정렬합니다.
Schedule(TimerTask 작업, 긴 지연): 지정된 지연 후에 지정된 작업의 실행을 예약합니다.
일정(TimerTask 작업, 긴 지연, 긴 기간): 지정된 지연부터 반복적으로 고정 지연 실행을 수행하도록 지정된 작업을 예약합니다.
동시에, ScheduleAtFixedRate 메소드도 오버로드됩니다. ScheduleAtFixedRate 메소드는 일정과 동일하지만 차이점은 나중에 분석됩니다.
ScheduleAtFixedRate(TimerTask task, Date firstTime, long period): 지정된 작업이 지정된 시간에 반복 고정 속도 실행을 시작하도록 예약합니다.
ScheduleAtFixedRate(TimerTask task, long Delay, long period): 지정된 지연 후에 고정 속도 반복 실행을 시작하도록 지정된 작업을 예약합니다.
TimerTask
TimerTask 클래스는 추상 클래스로 Timer에서 일회성 실행 또는 반복 실행을 위한 작업으로 배열합니다. 해당 타이머 작업에서 수행할 작업을 수행하는 데 사용되는 추상 메서드 run() 메서드가 있습니다. 따라서 각 특정 작업 클래스는 TimerTask를 상속한 다음 run() 메서드를 재정의해야 합니다.
또한 두 가지 비추상 메서드가 있습니다.
boolean cancel(): 이 타이머 작업을 취소합니다.
longcheduledExecutionTime(): 이 작업의 가장 최근 실제 실행의 예약된 실행 시간을 반환합니다.

2. 예시

2.1. 예약된 작업 실행 지연 시간 지정

public class TimerTest01 { 
 Timer timer; 
 public TimerTest01(int time){ 
  timer = new Timer(); 
  timer.schedule(new TimerTaskTest01(), time * 1000); 
 } 
   
 public static void main(String[] args) { 
  System.out.println("timer begin...."); 
  new TimerTest01(3); 
 } 
} 
  
public class TimerTaskTest01 extends TimerTask{ 
  
 public void run() { 
  System.out.println("Time's up!!!!"); 
 } 
}
실행 결과:

첫 번째 인쇄:

timer begin....

3초 후 인쇄:

Time's up!!!!

2.2. 예약된 작업을 지정된 시간에 실행합니다. time

public class TimerTest02 {
 Timer timer;
   
 public TimerTest02(){
  Date time = getTime();
  System.out.println("指定时间time=" + time);
  timer = new Timer();
  timer.schedule(new TimerTaskTest02(), time);
 }
   
 public Date getTime(){
  Calendar calendar = Calendar.getInstance();
  calendar.set(Calendar.HOUR_OF_DAY, 11);
  calendar.set(Calendar.MINUTE, 39);
  calendar.set(Calendar.SECOND, 00);
  Date time = calendar.getTime();
    
  return time;
 }
   
 public static void main(String[] args) {
  new TimerTest02();
 }
}
  
public class TimerTaskTest02 extends TimerTask{
  
 @Override
 public void run() {
  System.out.println("指定时间执行线程任务...");
 }
}

스레드 작업은 시간이 11:39:00에 도달하면 실행됩니다. 물론 그 이상일 경우에도 실행됩니다. 그때보다! ! 실행 결과는

指定时间time=Tue Jun 10 11:39:00 CST 2014
指定时间执行线程任务...

2.3. 지정된 시간을 지연한 후 지정된 간격으로 예약된 작업을 주기적으로 실행합니다

public class TimerTest03 {
 Timer timer;
   
 public TimerTest03(){
  timer = new Timer();
  timer.schedule(new TimerTaskTest03(), 1000, 2000);
 }
   
 public static void main(String[] args) {
  new TimerTest03();
 }
}
  
public class TimerTaskTest03 extends TimerTask{
  
 @Override
 public void run() {
  Date date = new Date(this.scheduledExecutionTime());
  System.out.println("本次执行该线程的时间为:" + date);
 }
}

실행 결과:

本次执行该线程的时间为:Tue Jun 10 21:19:47 CST 2014
本次执行该线程的时间为:Tue Jun 10 21:19:49 CST 2014
本次执行该线程的时间为:Tue Jun 10 21:19:51 CST 2014
本次执行该线程的时间为:Tue Jun 10 21:19:53 CST 2014
本次执行该线程的时间为:Tue Jun 10 21:19:55 CST 2014
本次执行该线程的时间为:Tue Jun 10 21:19:57 CST 2014
.................

      对于这个线程任务,如果我们不将该任务停止,他会一直运行下去。
      对于上面三个实例,LZ只是简单的演示了一下,同时也没有讲解scheduleAtFixedRate方法的例子,其实该方法与schedule方法一样!
2.4、分析schedule和scheduleAtFixedRate
(1)schedule(TimerTask task, Date time)、schedule(TimerTask task, long delay)
      对于这两个方法而言,如果指定的计划执行时间scheduledExecutionTime(2)schedule(TimerTask task, Date firstTime, long period)、schedule(TimerTask task, long delay, long period)
      这两个方法与上面两个就有点儿不同的,前面提过Timer的计时器任务会因为前一个任务执行时间较长而延时。在这两个方法中,每一次执行的task的计划时间会随着前一个task的实际时间而发生改变,也就是scheduledExecutionTime(n+1)=realExecutionTime(n)+periodTime。也就是说如果第n个task由于某种情况导致这次的执行时间过程,最后导致systemCurrentTime>= scheduledExecutionTime(n+1),这是第n+1个task并不会因为到时了而执行,他会等待第n个task执行完之后再执行,那么这样势必会导致n+2个的执行实现scheduledExecutionTime放生改变即scheduledExecutionTime(n+2) = realExecutionTime(n+1)+periodTime。所以这两个方法更加注重保存间隔时间的稳定。
(3)scheduleAtFixedRate(TimerTask task, Date firstTime, long period)、scheduleAtFixedRate(TimerTask task, long delay, long period)
      在前面也提过scheduleAtFixedRate与schedule方法的侧重点不同,schedule方法侧重保存间隔时间的稳定,而scheduleAtFixedRate方法更加侧重于保持执行频率的稳定。为什么这么说,原因如下。在schedule方法中会因为前一个任务的延迟而导致其后面的定时任务延时,而scheduleAtFixedRate方法则不会,如果第n个task执行时间过长导致systemCurrentTime>= scheduledExecutionTime(n+1),则不会做任何等待他会立即执行第n+1个task,所以scheduleAtFixedRate方法执行时间的计算方法不同于schedule,而是scheduledExecutionTime(n)=firstExecuteTime +n*periodTime,该计算方法永远保持不变。所以scheduleAtFixedRate更加侧重于保持执行频率的稳定。

三、Timer的缺陷
3.1、Timer的缺陷
      Timer计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔个1秒执行任务),但是,Timer存在一些缺陷。首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。
(1)Timer管理时间延迟缺陷
      前面Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会发生一些缺陷:

public class TimerTest04 {
 private Timer timer;
 public long start; 
   
 public TimerTest04(){
  this.timer = new Timer();
  start = System.currentTimeMillis();
 }
   
 public void timerOne(){
  timer.schedule(new TimerTask() {
   public void run() {
    System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
    try {
     Thread.sleep(4000); //线程休眠3000
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }, 1000);
 }
   
 public void timerTwo(){
  timer.schedule(new TimerTask() {
   public void run() {
    System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
   }
  }, 3000);
 }
   
 public static void main(String[] args) throws Exception {
  TimerTest04 test = new TimerTest04();
    
  test.timerOne();
  test.timerTwo();
 }
}

   

      按照我们正常思路,timerTwo应该是在3s后执行,其结果应该是:

timerOne invoked ,the time:1001
timerOne invoked ,the time:3001

   

      但是事与愿违,timerOne由于sleep(4000),休眠了4S,同时Timer内部是一个线程,导致timeOne所需的时间超过了间隔时间,结果:

timerOne invoked ,the time:1000
timerOne invoked ,the time:5000

   

 
(2)Timer抛出异常缺陷
如果TimerTask抛出RuntimeException,Timer会终止所有任务的运行。如下:

public class TimerTest04 {
 private Timer timer;
   
 public TimerTest04(){
  this.timer = new Timer();
 }
   
 public void timerOne(){
  timer.schedule(new TimerTask() {
   public void run() {
    throw new RuntimeException();
   }
  }, 1000);
 }
   
 public void timerTwo(){
  timer.schedule(new TimerTask() {
     
   public void run() {
    System.out.println("我会不会执行呢??");
   }
  }, 1000);
 }
   
 public static void main(String[] args) {
  TimerTest04 test = new TimerTest04();
  test.timerOne();
  test.timerTwo();
 }
}

   

运行结果:timerOne抛出异常,导致timerTwo任务终止。

Exception in thread "Timer-0" java.lang.RuntimeException
 at com.chenssy.timer.TimerTest04$1.run(TimerTest04.java:25)
 at java.util.TimerThread.mainLoop(Timer.java:555)
 at java.util.TimerThread.run(Timer.java:505)

   

对于Timer的缺陷,我们可以考虑 ScheduledThreadPoolExecutor 来替代。Timer是基于绝对时间的,对系统时间比较敏感,而ScheduledThreadPoolExecutor 则是基于相对时间;Timer是内部是单一线程,而ScheduledThreadPoolExecutor内部是个线程池,所以可以支持多个任务并发执行。
3.2、用ScheduledExecutorService替代Timer
(1)解决问题一:

public class ScheduledExecutorTest {
 private ScheduledExecutorService scheduExec;
   
 public long start;
   
 ScheduledExecutorTest(){
  this.scheduExec = Executors.newScheduledThreadPool(2);
  this.start = System.currentTimeMillis();
 }
   
 public void timerOne(){
  scheduExec.schedule(new Runnable() {
   public void run() {
    System.out.println("timerOne,the time:" + (System.currentTimeMillis() - start));
    try {
     Thread.sleep(4000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  },1000,TimeUnit.MILLISECONDS);
 }
   
 public void timerTwo(){
  scheduExec.schedule(new Runnable() {
   public void run() {
    System.out.println("timerTwo,the time:" + (System.currentTimeMillis() - start));
   }
  },2000,TimeUnit.MILLISECONDS);
 }
   
 public static void main(String[] args) {
  ScheduledExecutorTest test = new ScheduledExecutorTest();
  test.timerOne();
  test.timerTwo();
 }
}

   

运行结果:

timerOne,the time:1003
timerTwo,the time:2005

   

(2)解决问题二

public class ScheduledExecutorTest {
 private ScheduledExecutorService scheduExec;
   
 public long start;
   
 ScheduledExecutorTest(){
  this.scheduExec = Executors.newScheduledThreadPool(2);
  this.start = System.currentTimeMillis();
 }
   
 public void timerOne(){
  scheduExec.schedule(new Runnable() {
   public void run() {
    throw new RuntimeException();
   }
  },1000,TimeUnit.MILLISECONDS);
 }
   
 public void timerTwo(){
  scheduExec.scheduleAtFixedRate(new Runnable() {
   public void run() {
    System.out.println("timerTwo invoked .....");
   }
  },2000,500,TimeUnit.MILLISECONDS);
 }
   
 public static void main(String[] args) {
  ScheduledExecutorTest test = new ScheduledExecutorTest();
  test.timerOne();
  test.timerTwo();
 }
}

   

运行结果:

timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
timerTwo invoked .....
........................

   


四、使用定时器实现弹弹球
模拟书上的一个例题做了一个弹弹球,是在画布上的指定位置画多个圆,经过一段的延时后,在附近位置重新画。使球看起来是动,通过JSpinner组件调节延时,来控制弹弹球的移动速度.
        BallsCanvas.java

public class BallsCanvas extends Canvas implements ActionListener,
  FocusListener {
  
 private Ball balls[]; // 多个球
 private Timer timer;
  
 private static class Ball {
  int x, y; // 坐标
  Color color; // 颜色
  boolean up, left; // 运动方向
  
  Ball(int x, int y, Color color) {
   this.x = x;
   this.y = y;
   this.color = color;
   up = left = false;
  }
 }
  
 public BallsCanvas(Color colors[], int delay) { // 初始化颜色、延时
  this.balls = new Ball[colors.length];
  for (int i = 0, x = 40; i < colors.length; i++, x += 40) {
   balls[i] = new Ball(x, x, colors[i]);
  }
  this.addFocusListener(this);
  timer = new Timer(delay, this); // 创建定时器对象,delay指定延时
  timer.start();
  
 }
  
 // 设置延时
 public void setDelay(int delay) {
  timer.setDelay(delay);
 }
  
 // 在canvas上面作画
 public void paint(Graphics g) {
  for (int i = 0; i < balls.length; i++) {
   g.setColor(balls[i].color); // 设置颜色
   balls[i].x = balls[i].left ? balls[i].x - 10 : balls[i].x + 10;
   if (balls[i].x < 0 || balls[i].x >= this.getWidth()) { // 到水平方向更改方向
    balls[i].left = !balls[i].left;
   }
  
   balls[i].y = balls[i].up ? balls[i].y - 10 : balls[i].y + 10;
   if (balls[i].y < 0 || balls[i].y >= this.getHeight()) { // 到垂直方向更改方向
    balls[i].up = !balls[i].up;
   }
   g.fillOval(balls[i].x, balls[i].y, 20, 20); // 画指定直径的圆
  }
 }
  
 // 定时器定时执行事件
 @Override
 public void actionPerformed(ActionEvent e) {
  repaint(); // 重画
 }
  
 // 获得焦点
 @Override
 public void focusGained(FocusEvent e) {
  timer.stop(); // 定时器停止
  
 }
  
 // 失去焦点
 @Override
 public void focusLost(FocusEvent e) {
  timer.restart(); // 定时器重启动
  
 }
}

   

BallsJFrame.java

class BallsJFrame extends JFrame implements ChangeListener {
  
  private BallsCanvas ball;
  private JSpinner spinner;
  
  public BallsJFrame() {
   super("弹弹球");
   this.setBounds(300, 200, 480, 360);
   this.setDefaultCloseOperation(EXIT_ON_CLOSE);
   Color colors[] = { Color.red, Color.green, Color.blue,
     Color.magenta, Color.cyan };
   ball = new BallsCanvas(colors, 100);
   this.getContentPane().add(ball);
  
   JPanel panel = new JPanel();
   this.getContentPane().add(panel, "South");
   panel.add(new JLabel("Delay"));
   spinner = new JSpinner();
   spinner.setValue(100);
   panel.add(spinner);
   spinner.addChangeListener(this);
   this.setVisible(true);
  }
  
  @Override
  public void stateChanged(ChangeEvent e) {
   // 修改JSpinner值时,单击JSpinner的Up或者down按钮时,或者在JSpinner中按Enter键
   ball.setDelay(Integer.parseInt("" + spinner.getValue()));
  
  }
  
 public static void main(String[] args) {
  new BallsJFrame();
 }
  
}

   

效果如下:

Java에서 타이머를 분석하고 타이머를 사용하여 핀볼 게임을 만듭니다.

解析Java中的定时器及使用定时器制作弹弹球游戏的示例

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:자바 타이머 예다음 기사:자바 타이머 예