在多线程技术中,用的较多的就是Timer计时器了,它本身不是Runnable的实现类,计划任务用另一个TimerTask类来实现。
应用场景:比如在报表统计中常常需要使用任务调度来更新报表库 。
Timer.schedule(TimerTask,Date)
我们用Timer的schedule方法来设置一个任务,Date为任务的执行时间。Timer.schedule(TimerTask,long),当第二个参数是long类型的时候,代表延迟自执行时间,当存在第三个参数的时候,代表周期。比如Timer.schedule(TimerTask,long,long)
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务执行了-" + new Date());
}
};
String timeStr = "2018-7-22 17:29:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse(timeStr);
timer.schedule(task, time);
}
}
输出结果:
任务执行了-Sun Jul 22 18:09:01 CST 2018
- 发现任务立即执行,这也就说明了如果任务设置时间早于当前时间,就立即执行任务。
我们再来看任务时间晚于当前时间的情况:
这里写代码片public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务执行了-" + new Date());
}
};
String timeStr = "2018-7-22 18:13:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse(timeStr);
timer.schedule(task, time);
}
}
输出结果:
任务执行了-Sun Jul 22 18:13:00 CST 2018
一个Timer设置多个Task
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务1执行了-" + new Date());
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("任务2执行了-" + new Date());
}
};
String timeStr = "2018-7-22 18:19:00";
String timeStr2 = "2018-7-22 18:20:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse(timeStr);
Date time2 = sdf.parse(timeStr2);
timer.schedule(task, time);
timer.schedule(task2, time2);
}
}
输出结果:
任务1执行了-Sun Jul 22 18:19:00 CST 2018
任务2执行了-Sun Jul 22 18:20:00 CST 2018
Timer定时器不会自动结束
当new Timer()之后,发现进程不会自动结束。因为Timer不是一个守护线程,main线程结束之后,它不会自动结束。
来看一下Timer的构造函数:
public Timer(String name) {
thread.setName(name);
thread.start();
}
方法一:设置为守护线程
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
Timer timer = new Timer(true);
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务1执行了-" + new Date());
}
};
System.out.println("现在时间-"+ new Date());
String timeStr = "2018-7-22 19:16:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse(timeStr);
timer.schedule(task, time);
}
}
输出结果:
现在时间-Sun Jul 22 19:15:36 CST 2018
然后就直接结束了
发现如果设置成守护线程,那么主线程结束了,该任务也就得不到执行了。
方法二:停止等待
使用该方法,可以让主程序等待Task任务执行完成之后才执行后面的语句。这就让Timer起到了一个计时器的效果。
import java.text.ParseException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class T{
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException, ParseException {
Timer timer = new Timer();
final ReentrantLock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务1执行了-" + System.currentTimeMillis());
try{
lock.lock();
}finally{
condition.signal();
flag = false;
lock.unlock();
}
}
};
System.out.println("现在时间-"+ System.currentTimeMillis());
timer.schedule(task, 2000); //设置到2000ms后执行该任务
try{
lock.lock();
while(flag){
condition.await();
}
}finally{
lock.unlock();
}
timer.cancel();
System.out.println("任务执行完了,执行后面的语句");
}
}
以上的做法,完全可以在一个线程中完成,也就是说,我们完全可以让main线程sleep(2000)再来执行后面的语句。
方法三:直接释放Timer(Timer.cancle)
完全可以在task中调用timer的cancle()方法实现停止线程。
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
final Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务执行-" + System.currentTimeMillis());
timer.cancel();
}
};
timer.schedule(task, 2000);
System.out.println("main语句-"+System.currentTimeMillis());
}
}
显然这种做法更加合理。但是要值得注意的是,Timer.cancle()会清楚计时器内所有的task,并且停止timer中的线程等待垃圾回收器回收。
TimerTask.cancle()
清空当前任务,取消后续的周期执行。如果当前任务是周期性的,当调用TimerTask.cancle()方法后,它将得不到后续的周期性执行。
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
final Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务A执行-" + System.currentTimeMillis());
this.cancel(); //下个周期不会执行
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("任务B执行-" + System.currentTimeMillis());
}
};
//2000ms后执行,并且以1000ms为周期继续执行
timer.schedule(task, 2000,1000);
timer.schedule(task2, 2000,1000);
}
}
输出结果:
任务A执行-1532269174240
任务B执行-1532269174240
任务B执行-1532269175240
任务B执行-1532269176241
任务B执行-1532269177241
周期时间小于任务执行时间
当周期时间小于任务执行时间的时候,schedule()的周期改成任务执行的时间为周期。例如,任务执行时间为3000ms,周期为2000ms,那么周期将会变成3000ms。
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
final Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务A执行-" + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//2000ms后执行,并且以2000ms为周期继续执行
timer.schedule(task, 2000,2000);
}
}
输出结果:
任务A执行-Mon Jul 23 00:24:19 CST 2018
任务A执行-Mon Jul 23 00:24:22 CST 2018
任务A执行-Mon Jul 23 00:24:25 CST 2018
任务A执行-Mon Jul 23 00:24:28 CST 2018
Timer.schedule()和Timer.scheduleAtFixedRate()
值得注意的是,如果计划的时间设置在过去,那么就会立即执行。Timer.schedule()和Timer.scheduleAtFixedRate()的都是设置计划,那么它们的区别在哪呢?
public class T{
public static void main(String[] args) throws InterruptedException, ParseException {
final Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务A执行-" + new Date());
}
};
TimerTask task2 = new TimerTask(){
@Override
public void run() {
System.out.println("任务B执行-" + new Date());
}
};
//2000ms后执行,并且以1000ms为周期继续执行
System.out.println(new Date());
String date = "2018-07-23 14:06:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse(date);
timer.schedule(task, time, 3*60*1000);
timer.scheduleAtFixedRate(task2, time, 3*60*1000);
}
}
输出结果:
Mon Jul 23 14:07:21 CST 2018
任务A执行-Mon Jul 23 14:07:21 CST 2018
任务B执行-Mon Jul 23 14:07:21 CST 2018
任务B执行-Mon Jul 23 14:09:00 CST 2018
任务A执行-Mon Jul 23 14:10:21 CST 2018
任务B执行-Mon Jul 23 14:12:00 CST 2018
任务A执行-Mon Jul 23 14:13:21 CST 2018
- 输出结果分析:
我们设置的时间是 06分,但是现在的时间是07分,但是我们发现09的时候B计划执行了,也就是说Timer.scheduleAtFixedRate()设置的3分钟周期是从06分开始的,而Timer.schedule()设置的3分钟周期是从07分开始的。
- 区别:
scheduleAtFixedRate()是从计划时间开始计算,schedule()是从第一次执行器开始计算。
共同点是,如果任务时间设置到过去,那么都会立即执行。
这里还应该注意一点,如果过去时间过去太久,已经经过了好几个周期,scheduleAtFixedRate()会追赶弥补,schedule()不会追赶弥补 -
应用场景
A的数据量极大,B库的数据来自于A库,我们提取A库中的数据进行统计制作报表,如果每次查看报表都去A库中提取,效率很低,这时候需要定时从A库中周期的提取数据到B库,这时候可以用到该计时器设定TimerTask定时执行任务。