Java Web 基础篇 L5 Java实现定时器

1Java实现一个定时器

1.1 定时器的作用和需求

在某些场景下,常常需要定时的功能,如商城的到整点开启商品秒杀功能,这个功能就可以使用定时器来调用一个方法完成,将当前时间到指定时间的差值作为定时器的延时时间,等定时器等待了确认的延时时间后,将调用一个方法,把秒杀系统的页面呈现给用户

因此,这个定时器应该实现:

1,制定一个时间间隔
2,在到达规定时间后,能自动调用某个任务

重点在于:定时的精确性,应该尽可能地提高定时精度
		 不要受到执行任务的干扰

1.2 定时器的简单实现

最简单的方法是当开启定时器后,直接执行指定的任务即可

/**
 * @author 雫
 * @date 2021/4/17 - 12:21
 * @function
 */
public abstract class SimpleTimer implements Runnable {
    // 定时调用时间 每隔time间隔 执行指定任务
    private long time;

    // 决定定时器是否工作
    private volatile boolean goon;

    // 抽象方法 待完成
    public abstract void job() throws InterruptedException;


    public SimpleTimer() {}
    

    public void setTime(long time) {
        this.time = time;
    }

    // 开启定时器
    public void start() {
        if(this.goon == true) {
            return;
        }
        this.goon = true;
        new Thread(this).start();
    }

    // 关闭定时器
    public void terminate() {
        if(this.goon == false) {
            return;
        }
        this.goon = false;
    }

    @Override
    public void run() {
        while (this.goon) {
            try {
                Thread.sleep(this.time);
                job();
            } catch (InterruptedException e) {}
        }
    }

}

测试:
在这里插入图片描述
这里要求定时器只要开始工作后,每隔1s输出一次当前时间,5s后关闭定时器,理想的情况下,是每次输出的时间比上次多1000ms,查看上述的输出结果:

1618635216238
1618635217241 ->1003ms
1618635218254 ->1013ms
1618635219264 ->1010ms
1618635220276 ->1012ms

根据上述的输出结果,可以看到误差在10ms左右,现在我们看另一种情况,更改job()内的代码,让job()每次输出完时间后,随机等待一段时间:
在这里插入图片描述
查看输出结果:

1618635520403
1618635522244->1841
1618635523365->1121
1618635524394->1029

本次的输入,job()不但少执行了一次,而且最大的误差达到了841ms,这样的定时器是不精确,不能用的

问题在于:

/*
    * 下面的定时器是存在巨大缺陷的
    * 理想是每隔time时间段后执行一次job 
    * 哪怕上次的job没有执行完 时间到了也要开启下一次job 
    * 但是这里的却是每隔time+job所需时间后 才能开启下一次任务 
    * 执行job占用了时间 因此定时器不再精确
    * */
    @Override
    public void run() {
        while (this.goon) {
            try {
                Thread.sleep(this.time);
                job();
            } catch (InterruptedException e) {}
        }
    }

因此一个线程是不够用的,定时功能和调用方法的功能应该独立运行,定时器的作用是时间到了后,让另一个线程去调用方法

1.3 单线程执行任务的定时器

开启一个线程作为定时器,只使用一个线程来执行任务,每次执行完任务后任务线程进入休眠,需要执行下一次任务时,再唤醒任务线程,但是执行任务所需的时间必须小于定时间隔

任务接口:
/**
 * @author 雫
 * @date 2021/4/17 - 13:15
 * @function 任务接口
 */
public interface Task {
    void job();
}

Task的实现类对象应该作为工作线程的成员
以便工作线程被唤醒后,开始调用job()来完成工作,工作线程类:
/**
 * @author 雫
 * @date 2021/4/17 - 13:27
 * @function 单线程执行任务
 */
public class SingleThreadTask implements Runnable {
    private Task task;

    private volatile boolean goon;

    public SingleThreadTask(Task task) {
        this.task = task;
        this.goon = false;
    }

    void start() {
       this.goon = true;
       // 开启工作线程
       new Thread(this).start();
    }

    void stop() {
        this.goon = false;
    }

    // 执行任务的线程
    @Override
    public void run() {
        /*
        * 起始时阻塞自己,被唤醒后再执行任务,执行完后再阻塞自己
        * 该工作线程被唤醒时,才执行job() 执行完后阻塞自己 再等待被唤醒
        * 被唤醒的时机就是定时器达到了下一个时间间隔
        * */
        while (this.goon) {
            synchronized (this) {
                try {
                    this.wait();
                    this.task.job();
                } catch (InterruptedException e) {}
            }
        }
    }
    
}

工作线程类应该作为定时器类的成员,时间间隔到达时,唤醒工作线程

/**
 * @author 雫
 * @date 2021/4/17 - 13:16
 * @function 单线程执行任务的定时器
 */
public class SingleThreadTimer implements Runnable {
    private long time;
    private volatile boolean goon;
    private Task task;

    // 工作线程
    private SingleThreadTask singleThreadTask;

    // 对象锁
    private volatile Object lock;



    public SingleThreadTimer() {
        this.lock = new Object();
    }

    public SingleThreadTimer(long time, Task task) {
        this();
        this.time = time;
        this.task = task;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public void setTask(Task task) {
        this.task = task;
    }

    public void start() {
        if(this.task == null) {
            System.out.println("未设置任务");
            return;
        }
        if(this.goon) {
            return;
        }
        // 开启工作线程,工作线程此时处于阻塞状态,等待被唤醒后再工作
        singleThreadTask = new SingleThreadTask(this.task);
        singleThreadTask.start();

        this.goon = true;
        // 开启定时器
        new Thread(this).start();
    }

    public void terminate() {
        if(!this.goon) {
            return;
        }
        this.goon = false;
    }

	// 开启定时线程
    @Override
    public void run() {
        while (this.goon) {
            synchronized (this.lock) {
                try {
                    /*
                    * 定时器每隔time时间片段后 去唤醒工作线程
                    * 工作线程不再阻塞,开始执行job 执行完后将自己阻塞 
                    * 等待下一次被唤醒
                    * */
                    this.lock.wait(this.time);
                } catch (InterruptedException e) {}

                synchronized (this.singleThreadTask) {
                    this.singleThreadTask.notify();
                }
            }
        }
        // 结束后 关闭任务线程
        this.singleThreadTask.stop();
    }

}

测试:
在这里插入图片描述
执行结果:

1618639296949
1618639297954->1005
1618639298963->1009
1618639299974->1011
1618639300986->1012

执行结果的无参大概在10ms左右,使用单线程执行任务可以避免创建过多的线程,避免系统资源被浪费,始终只有一个线程在工作

但是单线程执行任务的时间必须要小于定时间隔,如果时间间隔是1000ms,执行一个任务需要1500ms,当第一个任务线程执行到1000ms时,定时线程尝试去唤醒任务线程,此时任务线程还在执行,唤醒失败,且本次的任务执行完后,才能有机会执行下一次任务

单线程执行任务是不稳定的

1.4 多线程执行任务的定时器

开启一个线程作为定时器,每隔一段时间开启一个任务线程,不管上次的任务有没有结束,本次将会开启一个新线程来执行任务

/**
 * @author 雫
 * @date 2021/4/17 - 13:15
 * @function 任务接口
 */
public interface Task {
    void job();
}

/**
 * @author 雫
 * @date 2021/4/17 - 14:09
 * @function 多线程执行任务
 */
public class ManyThreadTimer implements Runnable {
    private long time;
    private volatile boolean goon;
    private Task task;

    public ManyThreadTimer(long time, Task task) {
        this.time = time;
        this.task = task;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public void setTask(Task task) {
        this.task = task;
    }

    public void start() {
        if(goon) {
            return;
        }

        this.goon = true;
        new Thread(this).start();
    }

    public void terminate() {
        if(!goon) {
            return;
        }

        this.goon = false;
    }

    /*
    * 定时线程每经过一段时间,开启一个执行任务的线程
    * 执行任务的时间与定时时间无关
    * */
    @Override
    public void run() {
        while (this.goon) {
                try {
                    Thread.sleep(this.time);
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            task.job();
                        }
                    }).start();
                } catch (InterruptedException e) {}
            }
        }

}

测试:
在这里插入图片描述

输出结果:

1618640693032
1618640694036->1004
1618640695050->1004
1618640696062->1012
1618640697070->1008

误差保持在5-10ms,并且使用多线程执行任务,定时器与任务线程是完全分开的,无论任务线程要执行多久,定时器只要时间间隔到了,就会开启新的线程,不管上一个线程有没有执行完毕

因为创建了多个线程,采用线程池来管理:

/**
 * @author 雫
 * @date 2021/4/17 - 14:09
 * @function 多线程执行任务
 */
@SuppressWarnings("all")
public class ManyThreadTimer implements Runnable {
    private long time;
    private volatile boolean goon;
    private Task task;

    // 线程池保存多个线程
    private ThreadPoolExecutor threadPoolExecutor;

    public ManyThreadTimer(long time, Task task) {
        this.time = time;
        this.task = task;
        threadPoolExecutor = new ThreadPoolExecutor(5,
                                    10,
                                    10,
                                    TimeUnit.SECONDS,
                                    new ArrayBlockingQueue<>(10));
    }

    public void setTime(long time) {
        this.time = time;
    }

    public void setTask(Task task) {
        this.task = task;
    }

    public void start() {
        if(goon) {
            return;
        }

        this.goon = true;
        new Thread(this).start();
    }

    public void terminate() {
        if(!goon) {
            return;
        }

        this.goon = false;
    }

    /*
    * 定时线程每经过一段时间,开启一个执行任务的线程
    * 执行任务的时间与定时时间无关
    * */
    @Override
    public void run() {
        while (this.goon) {
                try {
                    Thread.sleep(this.time);
                    // 使用线程池 避免多次重复创建线程
                    this.threadPoolExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            task.job();
                        }
                    });
                } catch (InterruptedException e) {}
            }
        // 关闭定时器的同时关闭线程池
        this.threadPoolExecutor.shutdown();
    }

}

1.5 定时器小结

为了精准地在一段时间后执行某个任务,采用一个线程控制时间,每隔一段时间开启一个新线程就可以顺利执行,这样的方式不论任务需要执行多久,时间到了,定时器也会开启新的线程执行任务,配合上线程池,可以避免重复创建线程,需要注意的是执行的任务运行在多线程环境中

这样的定时器可以用于B/S模式中的轮询和各种与指定时间有关的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值