【JaveEE】——(手把手教你)用IDEA手搓一个定时器Timer

简介: 手把手教你用IDEA自己实现一个定时器,IDEA中的定时器,schedule方法,实现过程中引发的线程安全问题和解决方式,

 阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:什么是定时器

二:IDEA中的定时器Timer

1:实例化Timer

2:.schedule()方法

(1)分析

(2)具体实现

3:Timer内部前台线程

三: 自己实现定时器

1:思路

(1)阻塞队列放任务

(2)线程扫描任务

四:代码超详细解读

五:多线程安全问题

问题一:进出元素安全问题

问题二:线程饿死问题

问题三:wait和while捆绑使用

问题四:忙等

情况①:

情况②:


一:什么是定时器

前引:定时器,顾名思义,就是我们建立一个多久时间后需要执行的任务

打个比方,现在是早上8点,我告诉闹钟2个小时后提醒我该去上课了~,到10点的时候,闹钟就执行这个任务——“提醒我去上课”

代码示例:

package thread;
import java.util.Timer;
import java.util.TimerTask;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-26
 * Time: 8:44
 */
public class ThreadDemon32 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务1,时间为1秒后");
            }
        }, 1000);
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                System.out.println("执行任务2,时间为2秒后");
            }
        },2000);
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                System.out.println("执行任务3,时间为3秒后");
            }
        },3000);
    }
}

image.gif

二:IDEA中的定时器Timer

1:实例化Timer

image.gif 编辑

2:.schedule()方法

(1)分析

image.gif 编辑

注意导包

(2)具体实现

image.gif 编辑

image.gif 编辑

3:Timer内部前台线程

从上面的运行结果不难发现,我们的main函数执行完毕了,任务也都打印出来了,但是进程还在运行,就是因为Timer内部自带有前台线程

三: 自己实现定时器

1:思路

(1)阻塞队列放任务

首先需要一个阻塞队列来放所有schedule里的任务

注:这个阻塞队列一定是一个优先级队列,通过比较器来比较,delay的大小,来安排队列顺序,执行时间小的放前面,大的放后面

PriorityQueue(线程不安全,但是可以手动加锁)

PriorityBlockingQueue(线程安全,但是不太好人为控制)

(2)线程扫描任务

然后需要一个线程来扫描任务队列,负责计时,保证到时间了,任务出队列,来执行

四:代码超详细解读

(看不懂的按照步骤自己先敲一遍)(难度很大,敲完了会爽的起飞~~~)

package thread;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-26
 * Time: 9:36
 */
//1:首先创建一个定时器类
class MyTimer{
    //2:需要一个线程来扫描队列(只需要指向队列中的队首元素)
    private Thread t = null;
    //3:创建一个优先级队列,思考创建完了,就得放任务进去
    //10:把任务作为泛型放进队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue();
    //23:这里main方法中添加任务和MyTimer中出任务会引发多线程安全问题,这里我们使用锁对象来保护队列,那么在哪里加锁比较合适呢
    Object locker = new Object();
    //13:需要去写一个方法,把参数传入
    public void schedule(Runnable runnable , long delay){
        //24.5:参与到锁竞争中的schedule也需要加上锁,因为涉及到队列offer操作
        synchronized(locker){
            //14:通过MyTimerTask构造
            MyTimerTask task = new MyTimerTask(runnable , delay);
            //15:再把任务放到队列里面
            queue.offer(task);
            //26:offer后有元素了,唤醒
            locker.notify();
        }
    }
    //16:通过构造方法,让t线程扫描判断队首任务是否到达时间
    public MyTimer(){
        t = new Thread(()->{
    //17:t线程去扫描队首元素,如果时间到就删除队首元素,并执行任务,如果没到就等待
            while(true){
    //24:加锁,得加到while循环里面来,如果加到外面,当我们在main方法中newMyTimer的时候
    //就会调用构造方法加锁,然后一直while循环出不来,解不了锁,进而参与锁竞争中的schedule就解不了锁
                try{
                    synchronized(locker){
                        //17.5:如果队列为空等待
                        //27:While和notify捆绑使用,所以把if修改为while保险点,不懂的看阿华写的前面的文章哈
                        while (queue.isEmpty()){
                            //25:队列为空wait,catch异常,我们把try放到最外面
                            locker.wait();
                        }
                        //18:peek一下,获取队首元素
                        MyTimerTask task = queue.peek();
                        //19:记录下当前绝对时间
                        long curTime = System.currentTimeMillis();
                        //20:比较当前绝对时间和任务要执行的绝对时间大小
                        if(curTime >= task.getTime()){
                            //21:若大于等于,则出队列执行
                            queue.poll();
                            task.run();//此处顺序无所谓
                        }else {
                            //22:未到时间则等待
                            /*continue;*/
                            //28:省下资源
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}
//4:通过MyTimerTask这个类来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask>{
    //5:任务执行时间(绝对时间,ms级别的时间戳)
    private long time;
    //6:具体要执行的任务(runnable)
    private Runnable runnable;
    //20:获取一下任务执行的绝对时间
    public long getTime(){
        return time;
    }
    //7:写构造方法对成员变量初始化
    public MyTimerTask(Runnable runnable , long delay){
    //8:time(绝对时间 = 当前时间 + 设置的倒计时)
        this.time = System.currentTimeMillis() + delay ;
        this.runnable = runnable;
    }
    //9:通过一个方法来执行任务(调用runnable中的run方法)
    public void run(){
        runnable.run();
    }
    //11:实现接口,写比较器(导java.lang这个包)
    @Override
    public int compareTo(MyTimerTask o) {
        //12:返回时间小的那个任务(多试试)——至此为第一部分
        return (int)(this.time - o.time);
    }
}
public class ThreadDemon33 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第三次测试,等待的相对时间为三秒");
            }
        },3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第一次测试,等待的相对时间为一秒");
            }
        },1000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第二次测试,等待的相对时间为二秒");
            }
        },2000);
        System.out.println("hello main");
    }
}

image.gif

image.gif 编辑

五:多线程安全问题

问题一:进出元素安全问题

(1)添加和删除队列元素引起的线程安全问题——和while死锁

image.gif 编辑

(2)解决方式

image.gif 编辑

问题二:线程饿死问题

读懂上面的图之后,我们继续看哈

因为while循环执行的太快,我们刚解完锁,schedule还没拿上锁,就又被while循环里面给锁上了

所以我们引入wait,那么进而就得有notify,那么谁等待谁通知呢——队列为空wait,队列offer了就notify

image.gif 编辑

问题三:wait和while捆绑使用

if改为while

image.gif 编辑

问题四:忙等

当前执行时间还没到,即进入else语句中,如果里面是continue,则会跳出语句,重新循环,此过程非常的快,非常占用cpu的资源,然而还没什么卵用,cpu这就是“瞎忙活”——简称“忙等”

我们要做的就是在等待过程中,想办法释放cpu资源

这里用sleep不合适 image.gif 编辑

image.gif 编辑

用wait()带有超时时间的版本

情况①:

在wait期间,如果有新的任务添加进来,那我们的schedule就会唤醒wait,然后wait重新计算需要等待的时间

情况②:

在wait期间,没有新的任务天剑进来,那wait就会一直等待到,任务需要执行的绝对时间(这就是带有超时时间的版本的好处)自己唤醒自己

image.gif 编辑

image.gif 编辑

六:无注释版本全代码

package thread;
import java.util.PriorityQueue;
class MyTimer {//计时器中的线程负责扫描队列任务
    private Thread t = null;
    public Object locker = new Object();
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public void schedule(Runnable runnable , long delay){
       synchronized(locker){
           MyTimerTask task = new MyTimerTask(runnable , delay);
           queue.offer(task);
           locker.notify();
       }
    }
    public MyTimer(){//t线程去扫描队列,判断是否要执行任务
        t = new Thread(()->{
            while (true){
                try{
                    synchronized(locker){
                        while(queue.isEmpty()){
                            locker.wait();
                        }
                        //不为null拿到队首元素
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() >= task.getTime()  ){
                            queue.poll();
                            task.run();
                        }else {
                            locker.wait(task.getTime()-System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
}
class MyTimerTask implements Comparable<MyTimerTask>{//描述一个任务,并把这个任务扔到队列里面去
    private long time;//绝对时间
    private Runnable runnable;
    public long getTime(){
        return time;
    }
    public  MyTimerTask(Runnable runnable , long delay){
        this.time = System.currentTimeMillis() + delay;
        this.runnable = runnable;
    }
    public void run(){
        runnable.run();
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}
public class ThreadDemon33_2 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务一1s");
            }
        },1000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务二2秒");
            }
        },2000);
        System.out.println("main函数");
    }
}

image.gif


相关文章
|
7月前
|
存储 Linux C++
网易面试:手撕定时器
网易面试:手撕定时器
63 1
|
8月前
|
小程序 Android开发 iOS开发
【经验分享】如何手撸一个switch组件。
【经验分享】如何手撸一个switch组件。
68 7
|
8月前
MFC学习之路十三之定时器的使用
MFC学习之路十三之定时器的使用
158 0
|
安全 Java 关系型数据库
ESC简单使用心得体会
虽然计算机专业念了那么久,但是亲自动手部署一个云端项目的成就感还是满满的。虽然摘要存在的目的是概括全文核心思想,但我偏不,我就在这个模块发发自己感想。之前在Linux虚拟机或者是实体机中配置过各种环境, 遇到过各种问题,也通过多方搜索解决过各种问题, 但是,在云服务器上配置环境以及部署项这还是第一次尝试,虽然也遇到过一些问题,但是总体感觉上比在本地搭环境要更加顺畅一些。也许,这就是成熟产品高度集成化之后带来的便捷吧。
ESC简单使用心得体会
|
算法 Java 程序员
如何使用IDEA断点调试(debug), 用图文并茂的方式来教你
如何使用IDEA断点调试(debug), 用图文并茂的方式来教你
如何使用IDEA断点调试(debug), 用图文并茂的方式来教你
|
JavaScript
JS忍者秘籍中的定时器机制详解
前言 前段时间刚看完《JS忍者秘籍》,虽说是15年出版的,有些东西是过时了,但像对原型链、闭包、正则、定时器之类的机制却是不会过时的,里面很多东西都讲的很细,还是值得一读的,本文将对这本书中对定时器机制的部分进行详细的解析,如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。 个人博客了解一下:obkoro1.com 准备 阅读本文之前,推荐先阅读Js 的事件循环(Event Loop)机制以及实例讲解这篇文章来理解背后发生的事情,本文对事件循环机制不会很仔细的讲解。 定时器解决的问题: 由于JS的单线程特性,定时器提供了一种跳出单线程限制的方法。
195 0
JS忍者秘籍中的定时器机制详解
|
vr&ar 图形学
【Unity3D 灵巧小知识点】 ☀️ | Unity退出游戏代码
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
|
定位技术 vr&ar 图形学
【Unity3D 灵巧小知识点】 ☀️ | Unity中几个简单又常见的报错异常
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
【Unity3D 灵巧小知识点】 ☀️ | Unity中几个简单又常见的报错异常
|
设计模式 Java 程序员
别在再满屏的 if/ else 了,试试策略模式,真香!!
比如说对象的某个行为,在不同场景中有不同的实现方式,这样就可以将这些实现方式定义成一组策略,每个实现类对应一个策略,在不同的场景就使用不同的实现类,并且可以自由切换策略。
290 0
别在再满屏的 if/ else 了,试试策略模式,真香!!
IDEA设置背景图,让金轮马老师陪你敲代码
IDEA设置背景图,让金轮马老师陪你敲代码
126 0
IDEA设置背景图,让金轮马老师陪你敲代码