高并发--卷5--任务调度:Timer计时器

在多线程技术中,用的较多的就是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定时执行任务。

    猜你喜欢

    转载自blog.csdn.net/qq_25956141/article/details/81157017