Java当中的定时器

article/2025/11/7 2:27:02

目录

一、什么是定时器

二、Java当中的定时器

①schedule()方法:

②TimerTask

​编辑

  ③delay

三、实现一个定时器       

前提条件:

代码实现:

  ①确定一个“任务”(MyTask)的描述:

   ②schedule方法:

   ③需要一个计时器

        属性:

        构造方法:

         存在问题分析1:忙等:

      解决"忙等":

     存在问题分析二:notify()相比于wait()提前唤醒

        


一、什么是定时器

   给一个非常常见的场景:

   当我们在玩游戏,与其他玩家进行对战的时候,往往会出现下面的一些场景:

    我们点击了"vs"按键,进入了对战加载的页面。

    

 此时,如果在系统加载的过程当中,一直卡顿在某个时刻,无法前行。那么,难道程序就一直僵持住,无法行动了吗?


       并不是的,这个时候,后台会有一个"计时器“,如果加载的时间超过了设定的时间之后,就执行其他的任务。

       因此,总结一下,定时器本质就是一个拥有延时执行任务功能的工具。当达到指定的时间之后,可以执行定时器内部的任务。


二、Java当中的定时器

       Java当中就有一个定时器Timer.


①schedule()方法:

       定时器当中的核心方法就是:schedule()方法。

       这个方法,用于注册一个任务,并指定这个任务在调用schedule方法延时多长时间执行任务。


      可以看到,这个方法有两个参数:TimerTask task和long delay:下面将分析一下这两个参数


②TimerTask

   点进去这个类,可以看到这个是一个抽象类。

      Task的中文翻译就是任务。同时,这个抽象类也实现了Runnable接口,并且也把Runnable()接口当中的run方法继承了

     这说明TimeTask这个类描述了定时器需要执行的任务。


  ③delay

       确定延时的时间。当传入一个数字参数的时候,传入的毫秒数为这个定时器需要在调用schedule()方法之后多久执行自己的任务

     


    代码实现(采用匿名内部类的方式实现):

 public static void main(String[] args) {System.out.println("程序启动");Timer timer=new Timer();//schedule()方法是”安排“的意思//给定时器制定一个”任务“timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器的任务1");}},3000);}

   运行程序,可以看到,TimerTask当中的run方法在调用schedule方法之后的3000毫秒内被执行了:

    


三、实现一个定时器       

      场景描述,有3个线程,分别延时3000ms,2000ms,1000ms。

      如果想让延时越少的线程越先执行,那么应该如何初始化线程呢?

      首先,初始化延时3000ms任务的定时器,再初始化2000ms任务的定时器,最后初始化1000ms任务的定时器。

      这样,就可以让延时最短的线程最先执行:

             


    运行结果:


前提条件:

如果想自定义一个定时器来管理需要执行的任务,那么就需要下面两个条件:

 ①让注册的任务在指定的时间内被执行

 ②注册的多个任务按照最初计划的时间运行

   详细描述: 

A.需要一个数据结构,来保存一开始创建的N个任务。

B.我们这里指定的任务,都是带个"时间" 。即:多久之后才执行任务。

C.那么,就可以考虑使用优先级队列(PriorityQueue).

    按照时间的大小,建立小根据。

     时间越少的任务,优先级越高,越先执行。


代码实现:

       ①确定一个“任务”(MyTask)的描述:

        封装两个属性,一个是Runnable接口的引用,另外一个是delay,用于确定任务执行的时间:

        同时,也要描述出这个任务的"优先级"。因此要让这个"任务”实现comparable接口,拥有可以比较的能力:明确任务的优先级

  class MyTask implements Comparable<MyTask>{/*** 需要执行的任务*/private Runnable runnable;/*** 任务在什么时候执行:时间戳+延时时间*/private long delay;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;this.delay = delay;}/*** 获取延长时间* 延长的时间@return*/public long getDelay() {return delay;}/*** 执行任务*/public void run(){runnable.run();}/*** 实现一个比较器,让* 这个任务是可以比较的* 待比较的对象@param o* 确定小根堆的比较方式* @return*/@Overridepublic int compareTo(MyTask o) {//this比o小,那么返回小于0//谁的时间小,谁先执行return (int) (this.getDelay()-o.getDelay());}
}

 ②schedule方法:

  指定需要执行的任务,以及当前任务延迟多久执行:并且把任务存放到优先级阻塞队列(PriorityBlockingQueue当中)

/*** 指定两个参数* 第一个表示任务@param runnable* 第二个表示多久之后执行@param after*/public void schedule(Runnable runnable,long after){MyTask myTask=new MyTask(runnable,System.currentTimeMillis()+after);queue.put(myTask);}

 


  ③需要一个计时器

        属性:

         其中一个是存放任务的优先级阻塞队列(PriorityBlockingQueue):

         调用了schedule()方法之后传入的任务的优先队列
         优先级高(等待时间少)的任务先执行


 

        另外一个是扫描线程t


         扫描线程t的作用:

         一般情况下面,一个定时器(Timer)只需要初始化一次,然后就可以存放多个任务到定时器当中。

         扫描线程由于是在构造方法当中初始化的,那么,也就意味着扫描线程也只需要初始化一次。

         它的作用就是不断从阻塞队列当中取出任务(Task),并且执行。

        

         如果没有扫描线程,那么也就意味着一切的任务(Task)都需要由主线程来执行。


      代码实现: 

    /*** 扫描线程* 扫描线程:用于在构造方法当中初始化,并且执行从阻塞队列当中循环取出任务的线程**/private Thread t;/*** 一个阻塞的优先队列,用于保存* 调用了schedule()方法之后传入的任务的优先队列* 优先级高(等待时间少)的任务先执行*/private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();

        构造方法:

          A.初始化扫描线程t,并且指定它对应的任务

          B.在线程t的任务当中,需要不断循环从优先级的阻塞队列当中取出MyTask。

 

         并且进行判断:

         ①当前时间没有达到任务需要执行的时间的时候,就把任务放回优先级阻塞队列当中。

         ②如果当前时间已经达到了任务需要执行的时间,那么就执行对应的任务,调用myTask.run()方法。


 public MyTimer(){t=new Thread(()->{while (true){//取出队首元素,检查看看队首元素任务是否时间到了try {MyTask myTask=queue.take();//如果时间没有到,继续把任务放回到队列当中//获取现在的时间long curTime=System.currentTimeMillis();//拿现在的时间和myTask需要执行指定的时间进行对比//如果现在的时间没有到需要执行的时间,那么不执行任务,把这个任务放回到阻塞队列当中if(curTime<myTask.getDelay()){queue.put(myTask);}else {//如果到了需要执行的时间,那么执行对应的任务myTask.run();}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}

         存在问题分析1:忙等:

         当前时刻,如果达到了任务需要执行的时间,那么的确可以执行任务。

         如果没有达到任务需要执行的时间,那么,会不断循环地出现:

         把任务(task)从阻塞队列当中取出来,然后再把任务(task)放到阻塞队列当中的现象。

         由于我们这里使用的是优先级队列,因此,当把这个任务再次取出来的时候,仍然是堆顶的元素。

    

        这样的话, 假如一个任务需要在14:00执行,但是现在的时刻为13:00,远远没有到达需要执行的时间,那么,在这1h之内,将执行数以10亿次的循环,并且这个循环都是没有意义的。


        这种现象,在计算机当中,被称为"忙等"。也就是,等着任务执行,但是在等的这个过程当中,并没有"闲着”,而是不断重复没有意义的工作。

       按道理来说,等待的过程需要释放CPU资源的,但是这里并没有释放,因此造成了CPU资源的浪费。


      解决"忙等":

       针对上述代码,不要再执行忙等了,而是"阻塞式“等待。

       描述:

       当任务从计时器当中被取出来的时候,如果没有到达任务需要执行的时间,那么就让扫描线程进入阻塞等待的状态,等待的时间为当前时间与线程规定的时间之差

       也就是说,如果当前任务没有到达执行的时间,那么就需要等待到执行的时间。


       此处,可以考虑使用wait(timeout)或者sleep(timeout)两种方式来进行阻塞等待。那使用哪种方式比较好呢?

       使用wait()方法比较好,原因如下:

       如果使用了Thread.sleep(timeout)方法,那么也就意味着,扫描线程需要等待到timeout时间了,才可以重新工作。

       如果在这个期间,其他任务(MyTask)也被添加到了计时器当中,那么,它们将无法在timeout时间之前执行。

      

       因为,所有添加的任务执行,都是需要通过扫描线程来执行的。既然扫描线程都要wait()到对应的时间执行,那么新添加的任务,也不能执行了。


       因此,需要使用wait(timeout)方法来代替Thread.sleep(timeout)来执行对应的任务。

       当定时器当中有新的任务添加的时候,把扫描线程notify()了。

     代码实现: 


 

 

 


     存在问题分析二:notify()相比于wait()提前唤醒

     假如现在的时间为13:00

      在上图的代码当中,定时器存放了两个任务,其中一个任务是延时3000秒执行,另外一个是延时2000秒执行。

      假如,这一个定时器被创建好之后。某一时刻,扫描线程从定时器当中取出一个任务(MyTask)   


       当取出来之后(也就是红色箭头指向的部分),读取到了任务的执行时间为14:00,被判定为没有到指定的执行时间。

       但是,此时扫描线程被操作系统调度离开了cpu内核。


       此时,又有一个新的线程,调用了schedule()方法,往定时器当中存放任务。指定任务的执行时间为13:30。

       根据上面的写法,新的线程在执行schedule()方法快结束的时候,会notify()唤醒正在等待的扫描线程。


        图解:

时间轴扫描线程添加任务的线程
t1取出任务
t2判定时间,没有到达指定的时间
t3被调度离开cpu内核
t4调用schedule()方法添加任务到阻塞队列当中,并且notify()
t5回到cpu内核,并且执行wait(timeout)

         可以看到,新添加任务的线程,比扫描线程先执行了notify()方法。

         那么,也就意味着,t5时刻,执行的等待时间,是从13:00开始的,需要等待1h。那么,新添加的任务,也就是需要在14:30执行的任务,无法正常执行了。


        那如何解决这个问题,也就是让wait()方法优先执行呢?

       那就是,让"取出任务","判定时间","执行wait()"这三个操作变成原子的,保证take()和wait()操作之间不要有其他任务"贸然闯入"。

       图解:

时间轴扫描线程添加任务的线程
t1取出任务
t2判定时间,没有到达指定的时间
t3进行等待(timeout),wait()
t4添加任务
t5唤醒扫描线程:notify()

 

        代码实现:

     


 

        


http://chatgpt.dhexx.cn/article/3k6UbMNu.shtml

相关文章

【Java定时器】: Java创建定时器的三种方式(详细讲解)

Java创建定时器的三种方式 第一种&#xff0c;常见的thread&#xff0c;创建一个Thread让他让循环里一直执行&#xff0c;通过 Thread.sleep 来达到 定时任务的效果。 栗子如下&#xff1a; public static void main(String[] args) {final long timeTnterval 1000;Runnable …

Java定时器

目录 一、认识定时器 1、什么是定时器 2、标准库的定时器 二、模拟实现定时器 1、描述定时器中的任务 2、管理多个任务 3、扫描线程 4、优化 5、最终代码 一、认识定时器 1、什么是定时器 定时器是实际开发中常用的一个重要组件&#xff0c;类似于我们生活中的“闹钟…

matlab画图函数之plot【matlab图行绘制一】

plot函数 plot(x,y,’–gs’,‘LineWidth’,2,‘MarkerSize’,10,‘MarkerEdgeColor’,‘b’,‘MarkerFaceColor’,[0.5,0.5,0.5]) plot函数是最基本、最常用的绘图函数&#xff0c;用于绘制线性二维图。有多条曲线时&#xff0c;循环使用由坐标轴颜色顺序属性定义的颜色&…

Matlab画图线型、符号及颜色设置

1. matlab 中线条的主要属性 Color: 颜色LineStyle: 线型LineWidth: 线宽Marker: 标记点的形状MarkerFaceColor: 标记点填充颜色MarkerEdgeColor: 标记点边缘颜色MarkerSize: 标记点大小 2. 各种属性的名称 2.1 线型 -Solid line (default) – Dashed line : Dotted line …

matlab 画图基本

内容安排如下&#xff1a; 1、基本绘制&#xff08;图画大小、图形名称、图画背景、坐标轴名称、刻度范围、曲线颜色、坐标轴字体颜色等&#xff09;2、多条曲线&#xff08;plot hold on&#xff1b;plotyy&#xff1b;subplot&#xff1b;&#xff09;3、日期及时间轴绘图4、…

MATLAB画图详细教程

本文将详细介绍如何用matlab绘图并美化。 关于figure() 创建图窗窗口:figure() figure()的属性: Name:在标题栏显示的名称,接字符串,如Test Position:在电脑屏幕上的位置和大小,后接向量[left,bottom,width,height]也就是说指定了图窗的左下角位置,再向右+width、…

MATLAB画图总结

前言 在进行数据处理展示的时候&#xff0c;为了能直观体现实验的结果&#xff0c;需要进行绘图&#xff0c;让人们能直观的记住数据的走向特征&#xff0c;图像是结果的一种可视化展现&#xff0c;因此掌握一些绘图方法非常重要&#xff0c;使用MATLAB可以很简单的进行画图。下…

matlab 画图基本介绍

1.在命令窗口输入命令时&#xff0c;可以不必每输入一条命令就按enter键执行&#xff0c;可以在输入几行后一同运行。方法是&#xff1a;换行时&#xff0c;只要在按住<shift>键的同时按<enter>键即可&#xff0c;否则matlab就会执行上面输入的所有语句。 2.如何将…

matlab画图操作(修改坐标轴及字体,加粗,颜色修改,适合论文画图)

matlab常用画图操作 1.设置坐标轴2.设置figure大小3.matlab线条设置4.子图设置5.颜色查询6.colorbar设置7.线条透明度设置8.设置坐标轴刻度形式&#xff08;对数刻度&#xff09;9.图例设置10 文件保存11 消除白色边框12 添加子标题13 调换y轴递增顺序 1.设置坐标轴 %设置坐标…

使用matlab画图中图

又到一年论文季&#xff0c;没想到临近投稿的我居然会被图片的清晰度打败&#xff0c;需要子母图的时候&#xff0c;我直接使用powerpoint进行拼接&#xff0c;多次png另存为&#xff0c;图已经糊出了新高度&#xff0c;那种超级糊图在论文里应该是投不出去的吧。。。但是&…

matlab-画图对坐标的显示

前言 许多小朋友对于matlab画图函数再熟悉不过了&#xff0c;但是画图里面还有更细小的地方我们还得注意&#xff0c;对于坐标的显示也是我们在日常生活中常需要我们做的&#xff0c;下面我就将以一个例子1来说明在画图中显示坐标的两种形式。 下面的数据取样格式为 提示&a…

Matlab画图相关知识

目录 一、绘制不同种类的图像 1.1.画一般曲线图 1.2.绘制柱状图 二、matlab的图像处理 2.1将多张图同时绘制在一个Figure上面&#xff0c;采用subplot函数 2.2改变matlab图像坐标刻度增长幅度 2.3设置坐标轴刻度为任意值 2.4在一张Figure中用同一个x对应两个y作图 2.5关于…

MATLAB画图使用不同的线型、点及标记

转载自&#xff1a;MATLAB画图使用不同的线型、点及标记 (baidu.com) 一、 线型、连续标记 先从最普通的说起。在plot函数中指定线型。 tlinspace(0,5,20); x1 t; x2 2*t; x3 3*t; x4 4*t; plot(t,x1,b,t,x2,g-o,t,x3,r*,t,x4,c:d); 这是基础的比较简单的情况。不做…

MATLAB绘图

在MATLAB中绘制函数图形的步骤如下&#xff1a; 先定义变量 x&#xff0c;通过指定的变量 x 值的范围&#xff0c;该函数被绘制&#xff1b; 然后定义函数&#xff0c; y f(x)&#xff1b; 最后调用 plot 命令&#xff0c;如 plot(x, y)。 接下来我们通过例子绘制简单的函…

matlab画图入门教程

**matlab画图&#xff1a;**图像是数据结果的一种可视化表现&#xff0c;它能直观的体现你的数据结果&#xff0c;并且能体现你获得结果的准确性&#xff0c;在当前的大数据时代&#xff0c;在做数据分析的时候&#xff0c;将其可视化可以直观多维的展示数据&#xff0c;可以让…

MATLAB——画图(经典)

plot 二维线图全页折叠 #语法 plot(X,Y) plot(X,Y,LineSpec) plot(X1,Y1,…,Xn,Yn) plot(X1,Y1,LineSpec1,…,Xn,Yn,LineSpecn) plot(Y) plot(Y,LineSpec) plot(___,Name,Value) plot(ax,___) h plot(___) 说明 示例 plot(X,Y) 创建 Y 中数据对 X 中对应值的二维线图。 如果 …

MATLAB 画图

目录 图形对象属性 坐标轴 散点图 Line 属性 imagesc histogram 直方图 subplot 图像保存 其他 图形对象属性 set 设置图形对象属性 set(H,Name,Value)&#xff1a;为 H 标识的对象指定其 Name 属性的值 p plot(1:10); set(p,Color,red)% 更改特定线条的颜色gca 当…

【Matlab】MATLAB绘图

专题四 MATLAB绘图 绘图的目的是使数据可视化。 一 二维曲线 1. 函数plot() 在MATLAB中,函数plot()是最基本的绘图函数,利用它可以绘制出不同的二维曲线。函数plot()的基本用法: plot(x, y) % 其中,x和y分别用于存储x坐标和y坐标数据,通常x和y是长度相等的向…

Matlab 几种画图方式总结

函数形式/画图原理 1.显函数--- y f ( x ) 2.隐函数--- f (x , y ) 0 3.参数式--- x f ( x ) , y f ( y ) 4.极坐标--- 针对以上函数图像的绘制&#xff0c;有两种绘图方法&#xff1a; 1&#xff1a;找点画线&#xff1b; 2&#xff1a;根据定义域和函数关系画图&…

Matlab中的画图函数

目录 一、二维曲线和图形 1、二维图像基本命令plot (1). 曲线线型、颜色和标记点类型 (2). 设置曲线线宽、标记点大小&#xff0c;标记点边框颜色和标记点填充颜色等。 (3). 坐标轴设置 (4). 坐标轴刻度设置 (5). 图例 (6). 更多的设置 二、 图形的控制与表现 1&…