定时器(Timer)

1、定时器是什么?

1.1、概念

定时器是软件开发中一个重要的组件,类似于一个”闹钟“,达到一个设定的时间之后,就会执行某个指定的代码。

定时器是一种实际开发中非常常用的软件.

比如网络通信中,如果对方500ms没有返回数据,则断开连接尝试重连.

比如一个Map,希望里面的某个key在3s之后过期(自动删除).

类似以上场景就需要定时器.

1.2、标准库中的定时器 

*标准库中提供了一个Timer类,Timer类的核心方法为schedule.

*schedule包含两个参数,第一个参数指定即将要执行的任务代码,第二个参数指定多长时间后执行(单位为毫秒).

/**
 * JDK中的计时器
 */
public class Exe_01 {
    public static void main(String[] args) {
        //定义一个计时器
        Timer timer=new Timer();
        //往定时器里添加任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("全名星制作人们大家好,我是练习时长两年半的个人练习生蔡徐坤");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("喜欢唱、跳、rap、篮球");
            }
        }, 4000);
        timer.schedule(new TimerTask(){
            @Override
            public void run(){
                System.out.println("music!");
            }
        }, 6000);
    }
}

1.3自定义实现定时器

1、定时器中需要有一种数据结构来组织任务执行,可以考虑一个带优先级的阻塞队列,这是因为阻塞队列中的任务都有各自的执行时刻(delay),最先执行的任务一定是的delay最小的,使用带优先级的队列就可以高效的把这个delay最小的找出来。

2、需要单独定义一个类来描述具体的任务业务和执行时间。

3、需要一个专门线程来扫描数据结构中的任务到没有到执行时间。

4、需要一个方法去添加任务schedule()。

实现步骤:

1、描述任务:

具体的实现逻辑Runnable表示,执行的时间可以用一个long类型的time去记录

//描述定时器的执行任务
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    //定义执行器的执行时间
    private long time;
    public MyTask(Runnable runnable,Long time){
        if(time<0){
            throw new RuntimeException("延迟时间不能小于0");
        }
        this.runnable=runnable;
        //time记录当前任务的具体执行时间
        this.time=time+System.currentTimeMillis();
    }

2、组织任务:

用一个阻塞队列去组织任务

 

//使用优先级队列保存任务
private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();

 3、提供一个提交任务的方法

//定义一个添加任务的方法
public void schedule(Runnable runnable,Long time) throws InterruptedException {
    //封装成任务对象
    MyTask myTask=new MyTask(runnable,time);
    //加入队列中
    queue.put(myTask);
    //唤醒线程
    synchronized(locker){
        locker.notifyAll();
    }
}

4、创建一个扫描线程不停的去扫描队列是否有执行的任务

//创建扫描线程
Thread thread=new Thread(() ->{
    while(true){
        try {
            //1、取出任务
            MyTask task = queue.take();
            //2、判断任务时间是不是已经到了
            Long currentTime=System.currentTimeMillis();
            if(currentTime>=task.getTime()){
                //如果时间到了就执行任务
                task.getRunnable().run();
            }else{
                //如果时间没有到,就再写入队列
                queue.put(task);
                //计算一下执行任务时间与当前时间的差值
                long waitTime=task.getTime()-currentTime;
                synchronized(locker){
                    //继续等待waitTime时间
                    locker.wait(waitTime);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
},"taskScanner");
//启动线程
thread.start();

 

5、定时器的延迟时间不能为负数

public MyTask(Runnable runnable,Long time){
    if(time<0){
        throw new RuntimeException("延迟时间不能小于0");
    }
    this.runnable=runnable;
    //time记录当前任务的具体执行时间
    this.time=time+System.currentTimeMillis();
}

在任务的构造方法中加入时间不能为负的判断

 6、Long类型向int类型转换的过程中可能会存在溢出问题

@Override
public int compareTo(MyTask task) {
    //防止long转int的溢出问题
    if(this.time==task.getTime()){
        return 0;
    }
    if(this.time>task.getTime()){
        return 1;
    }else{
        return -1;
    }
}

 6、解决忙等问题

如果任务1执行完成之后,那么这个线程就会一直循环查看这个阻塞队列,不带停的。等到任务2的时间到了才判断出执行任务时间到了,那么中间这一段时间是没有必要去等的,而且非常的浪费CPU资源,这个现象就叫”忙等“。

如何解决:

1、取出任务判断一下如果没有到执行时间,计算一下,任务的执行时间与当前时间的差值。

2、把任务添加到队列中。

3、wait(等待差值时间),等待时间一到,刚好时执行任务的时间。

 

通过设置wait解决了忙等问题。 

7、解决无法唤醒问题

 在每次添加完任务之后加一个notify,来唤醒线程

 8、CPU调度过程中可能会产生执行顺序问题

 

造成上述现象是因为没有保证原子性

如何保证原子性? 

 完整代码: 



import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 自定义实现定时器
 */
public class MyTimer {
    //使用优先级队列保存任务
    private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    //定义一个锁对象
    Object locker=new Object();
    //定义一个添加任务的方法
    public void schedule(Runnable runnable,long time) throws InterruptedException {
        //封装成任务对象
        MyTask myTask=new MyTask(runnable,time);
        //加入队列中
        queue.put(myTask);
        //唤醒线程
        synchronized(locker){
            locker.notifyAll();
        }
    }
    public MyTimer(){
        //创建扫描线程
        Thread thread=new Thread(() ->{
            while(true){
                try {
                    synchronized (locker) {
                        //1、取出任务
                        MyTask task = queue.take();
                        //2、判断任务时间是不是已经到了
                        Long currentTime = System.currentTimeMillis();
                        if (currentTime >= task.getTime()) {
                            //如果时间到了就执行任务
                            task.getRunnable().run();
                        } else {
                            //如果时间没有到,就再写入队列
                            queue.put(task);
                            //计算一下执行任务时间与当前时间的差值
                            long waitTime = task.getTime() - currentTime;
                            synchronized (locker) {
                                //继续等待waitTime时间
                                locker.wait(waitTime);
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"taskScanner");
        //启动线程
        thread.start();
    }
}
//描述定时器的执行任务
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    //定义执行器的执行时间
    private long time;
    public MyTask(Runnable runnable,Long time){
        if(time<0){
            throw new RuntimeException("延迟时间不能小于0");
        }
        this.runnable=runnable;
        //time记录当前任务的具体执行时间
        this.time=time+System.currentTimeMillis();
    }
    @Override
    public int compareTo(MyTask task) {
        //防止long转int的溢出问题
        if(this.time==task.getTime()){
            return 0;
        }
        if(this.time>task.getTime()){
            return 1;
        }else{
            return -1;
        }
    }
    public Runnable getRunnable(){
        return runnable;
    }
    public Long getTime(){
        return time;
    }
}

/**
 * 测试定时器
 */
public class Exe_02 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个定时器对象
        MyTimer timer=new MyTimer();
        //添加定时任务
        timer.schedule(()->{
            System.out.println("任务1启动,三万次出枪");
        },1000);
        timer.schedule(()->{
            System.out.println("任务2启动,鸡与清风");
        },3000);
        timer.schedule(()->{
            System.out.println("任务3启动,三万出枪");
        },5000);
        timer.schedule(()->{
            System.out.println("任务4启动,三万出枪中");
        },8000);
        timer.schedule(()->{
            System.out.println("三万累了休息中");
        },6000);
        timer.schedule(()->{
            System.out.println("结束任务,三万收枪,执行时间3秒");
        },9000);
    }
}

运行结果:

解决死锁问题 :

代码示例:


public class Exe_03 {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("任务 1");
        }, 0);

        // 2 秒
        myTimer.schedule(() -> {
            System.out.println("任务 2");
        }, 0);

        myTimer.schedule(() -> {
            System.out.println("任务 3");
        }, 0);
    }
}

运行结果:

导致死锁的原因: 

1、在扫描线程中先拿到synchronized,获取阻塞队列的任务。

2、当阻塞队列为空的时间,阻塞队列进入wait状态,等待队列不为空时才能被唤醒。

3、于是代码就阻塞在了。

 

4、阻塞在这里,那么扫描线程的synchronized就无法退出,也就无法释放锁。

5、导致了schedule方法阻塞在synchronized获取锁资源里,无法向下执行代码。

6、两个线程都在等待,造成死锁现象。

这时,可以单独创建一个唤醒线程,让任务2和任务3不再出现死锁的现象,并且缩小执行线程任务synchronized的范围 

代码优化:


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 自定义实现定时器
 */
public class MyTimer {
    //使用优先级队列保存任务
    private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    //定义一个锁对象
    Object locker=new Object();
    //定义一个添加任务的方法
    public void schedule(Runnable runnable,long time) throws InterruptedException {
        //封装成任务对象
        MyTask myTask=new MyTask(runnable,time);
        //加入队列中
        queue.put(myTask);
        //唤醒线程
        synchronized(locker){
            locker.notifyAll();
        }
    }
    public MyTimer(){
        //创建扫描线程
        Thread thread=new Thread(() ->{
            while(true){
                try {
                        //1、取出任务
                        MyTask task = queue.take();
                        //2、判断任务时间是不是已经到了
                        Long currentTime = System.currentTimeMillis();
                        if (currentTime >= task.getTime()) {
                            //如果时间到了就执行任务
                            task.getRunnable().run();
                        } else {
                            //如果时间没有到,就再写入队列
                            queue.put(task);
                            //计算一下执行任务时间与当前时间的差值
                            long waitTime = task.getTime() - currentTime;
                            synchronized (locker) {
                                //继续等待waitTime时间
                                locker.wait(waitTime);
                            }
                        }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"taskScanner");
        //启动线程
        thread.start();
        //创建一个后台线程用来唤醒线程
        Thread daemonThread=new Thread(() ->{
            while(true){
                synchronized(locker){
                    //唤醒所有等待线程
                    locker.notifyAll();
                }
                try {
                    //睡眠一秒钟
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //设置为后台线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}
//描述定时器的执行任务
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    //定义执行器的执行时间
    private long time;
    public MyTask(Runnable runnable,Long time){
        if(time<0){
            throw new RuntimeException("延迟时间不能小于0");
        }
        this.runnable=runnable;
        //time记录当前任务的具体执行时间
        this.time=time+System.currentTimeMillis();
    }
    @Override
    public int compareTo(MyTask task) {
        //防止long转int的溢出问题
        if(this.time==task.getTime()){
            return 0;
        }
        if(this.time>task.getTime()){
            return 1;
        }else{
            return -1;
        }
    }
    public Runnable getRunnable(){
        return runnable;
    }
    public Long getTime(){
        return time;
    }
}

 将代码重新运行就会看到死锁现象已经被解决了。 

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值