工具-----滴答滴答(Java计时器)

Java计时器

一、作用:

计时器也就是用来计时的东西。当你需要隔一段时间去干一件事的时候就可以用到它,比如:

  1. 当某拼夕夕网站的抢购在一段规定的时间内需要结束时。
  2. 比如某宝的页面需要在一段规定的时间里刷新页面时。
  3. 当你设置的闹钟在规定时间将你叫醒。

而此时我们要做的事可以写成一个接口,由用户规定干什么事情。

二、实现原理:

  1. wait(); 调用此方法将使该线程进入阻塞态,直到被notify才会重新进入就绪态。
  2. wait(int gap); 调用此方法将使该线程进入阻塞态,直到被notify才会重新进入就绪态。设置了超时时间的wait方法一旦过了时间,并不需要其他线程执行notify也能自动解除阻塞。
  3. lock.notify();调用此方法将lock锁上的线程唤醒;

对于sleep:sleep()方法在睡眠的时候一直占用锁资源,也就是说,sleep睡眠时间没有到,其它线程就得不到锁,无法进入就绪态,得不到CPU的使用权。因此使用sleep()方法会浪费CPU的资源,所以后面使用wait()方法。

三、方案阐述即代码展示:

  • 方案:
  1. 首先定义一个接口,此接口中的方法时使用者自己写的要定时执行的任务。
  2. 其次新建一个类有两个线程,一个线程用来计时,一个线程用来执行任务。

(对于为什么给两个线程?答:因为若只有一个线程即计时也执行任务,但要考虑到任务的执行也是需要占用时间的,会造成计时误差。所以开辟出两个线程去执行)

  • 代码显示:
package com.smy.timer.didadida;

public interface IDidadida {
        private static final int DEFAULT_dELAY_tIME = 1000;
	void doSomething();
}
package com.smy.timer.didadida;

import java.util.concurrent.ScheduledThreadPoolExecutor;

public class Didadida implements Runnable { 
        //延迟时间   
	private int delayTime;
        //对象锁
	private Object lock;
        //开始计时和结束计时的标志
	private volatile boolean goon;
        //接口(接口中的方法即任务)
	private IDidadida didadida;
	
	public Didadida() {
		this(DEFAULT_dELAY_tIME, null);
	}
	
        //初始化
	public Didadida(int delayTime, IDidadida didadida) {
		lock = new Object();
		this.delayTime = delayTime;
		this.didadida = didadida;
	}
        
        //可以由用户来设置
	public void setDidadida(IDidadida didadida) {
		this.didadida = didadida;
	}
	
        //开始计时
	public void startTimer() {
		this.goon = true;
                //开启两个线程
		new Thread(new TimeWorker(), "timeWorker").start();
		new Thread(this, "didadida").start();
	}
	
        //结束计时
	public void endTimer() {
		if (goon == false) {
			System.out.println("now had ended");
			return;
		}
		
		if (didadida == null) {
			System.out.println("无事可做!");
			return;
		}
		
		this.goon = false;
	}
        
        //用来计时的线程
	@Override
	public void run() {
		while (goon) {
			synchronized (lock) {
				try {
					lock.wait(delayTime);
					//这里是唤醒一个线程,用来专门执行任务
					lock.notify();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

        //内部类(用来执行任务)
	class TimeWorker implements Runnable {
		
		@Override
		public void run() {
			while (goon) {
				synchronized (lock) {
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				didadida.doSomething();
			}
		}
		
	}
}

当然我们要看的核心当然是两个线程的执行:就是关于锁

首先,两个线程我们当然要让计时线程先跑起来,但由于时间片和优先级等的不确定性,我们必须用锁来加以限制。

  1. 给执行线程和计时线程都加锁,但在执行线程的一开始wait();这样就算执行线程得到CPU也可以先阻塞它。
  2. 而计时线程必将进入自己的锁块,并wait(delayTime);后再去唤醒执行线程执行dosomething方法。

注:此代码有两个需要注意的地方

  1. 若执行任务线程中执行dosomething的时间过长,还没有进入阻塞态,而计时线程已经去notify了,这时会无线程可唤醒。只能等下次线程去唤醒,所以当wait时间和dosomething的执行时间差距过大,将会产生成倍时间的误差,比如wait一秒,但是执行dosomething需要两秒,这时在第1秒去notify时还没执行完在第2秒notify时才会再去执行,这时我们设置的时间虽然是一秒,但其实是两秒(0秒时执行,2秒时执行......)。所以此时我们的工具是有误差的。
  2. dosomething方法必须写在锁块的外面,若在锁块里面,就必须等待执行完dosomething才能释放锁,这样就会产生计时误差。

 四、Test测试:

package com.smy.timer.didadida.test;

import java.text.SimpleDateFormat;

import com.smy.timer.didadida.Didadida;
import com.smy.timer.didadida.IDidadida;

public class Test {
	
	public static void main(String[] args) {
		Didadida didadida = new Didadida(1000, new IDidadida() {
			
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
			
			@Override
			public void doSomething() {
		        String data = sdf.format(System.currentTimeMillis());
				System.out.println("-------" + data);
			}
		});
		System.out.println("开始");
		didadida.startTimer();
	
//		didadida.endTimer();
	}
	
}

结果:给的参数是一秒

下面给出其缺陷的Test:

package com.smy.timer.didadida.test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;

import com.smy.timer.didadida.Didadida;
import com.smy.timer.didadida.IDidadida;

public class Test {
	
	public static void main(String[] args) {
		
		
		Didadida didadida = new Didadida(1000, new IDidadida() {
			File file = new File("E:\\test.txt");
			
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
			
			@Override
			public void doSomething() {
				FileOutputStream fileOutputStream = null;
				try {
					fileOutputStream = new FileOutputStream(file);
				} catch (FileNotFoundException e1) {
					e1.printStackTrace();
				}
				for (int i = 0; i < 1000000; i++) {
					try {
						fileOutputStream.write(i);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				
		        String data = sdf.format(System.currentTimeMillis());
				System.out.println("-------" + data);
				try {
					fileOutputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
		System.out.println("开始");
		didadida.startTimer();
	
//		didadida.endTimer();
	}
	
}

 

可以看出是五秒一次即1的整数倍。 

猜你喜欢

转载自blog.csdn.net/baidu_41922630/article/details/103188345
今日推荐