Java多线程案例——定时器

news/2024/11/13 3:35:51/

一,定时器

1.定时器的概念

定时器是Java开发中一个重要的组件(功能类似于闹钟),可以指定一个任务在多长时间后执行(尤其在网络编程的时候,如果网络卡顿很长时间没有响应用户的需求,此时可以使用定时器来终止用户的请求),所以一个定时器最少具有两个功能:

  1. 一个需要执行的任务

  1. 指定的时间去执行任务

在Java标准中提供了Timer类来封装定时器这样的功能。

2.Timer类

Timer类是Java标准库提供的内置类,其核心方法是schedule方法;

Timer类构造方法

Timer()

创建一个新的计时器

Time(boolean isDaemon)

创建一个新的定时器,其相关线程可以指定为 run as a deamon

Time(String name)

创建一个新的定时器,其相关线程具有指定的名称

Time(String name, boolean isDaemon)

创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a deamo

Timer类的构造方法一共有4种,一般最常用的就是第一种无参的构造方法

schedule方法

schedule方法是Timer类的核心,定时器所执行的操作都是由该方法提供,所以我们来看一下schedule方法的源码:

  1. 该方法是一个普通方法,在使用前需要先创建Timer类对象

  1. 该方法有两个参数:第一个是TimerTask task(重写内部的run方法来指定定时器的任务),第二个是long delay(描述的是多长时间后执行该任务,单位是ms)

  1. 其中这里的TimerTask类似于之前创建的线程中的Runnable,都是指定一个具体的任务

定时器任务示例

/*** 创建一个定时器任务* 任务内容:输出hello world* 时间:1000ms之后执行*/
import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo2 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello world");}},1000);}
}

执行结果:

程序将会在1000ms毫秒之后打印“hello word”,但是此时进程并没有结束,因为定时器创建出来的线程默认属于前台线程,前台线程会阻止进程的结束。

二,模拟实现简单的定时器

定时器需要满足两个功能:

1.让被注册的任务能够在指定的时间被执行

2.一个定时器可以注册N个任务,N个任务按照最初约定的时间,按顺序执行

解决办法:

  1. 对于第一个功能,我们只需要单独在定时器内部搞个线程,让这个线程周期性的扫描,判定任务是否到时间了,如果到时间了就执行,否则就不执行

  1. 对于第二个问题,我们需要一个数据结构来保存这些任务,因为任务需要按照时间的先后顺序来执行,所以这里采用优先级队列来保存任务(同时这个优先级队列需要按照指定的优先级来保存),又因为在多线程的环境下使用定时器,所以优先级队列需要带有阻塞功能,即采用PriorityBlockingQueue这样的数据结构。

代码如下:

package ThreadLearning;import java.util.concurrent.PriorityBlockingQueue;/*** 使用一个类来表示一个定时器中的任务*/
class MyTask implements Comparable<MyTask> {//要执行的任务private Runnable runnable;//任务啥时候执行(使用毫秒时间戳表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}//获取当前任务的时间public long getTime() {return time;}//执行时间public void run() {runnable.run();}//重写compareTo方法来指定优先级队列的优先级@Overridepublic int compareTo(MyTask o) {return (int) (this.time - o.time);}
}class MyTimer {//扫描线程private Thread t = null;//阻塞队列用来保存任务//传入的参数是MyTask这个类private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(() -> {while (true) {try {synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {//还没到点,先不执行queue.put(myTask);//设置wait的等待时间,即队首元素与现在时间的差值this.wait(myTask.getTime() - curTime);} else {//时间到了,执行任务myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}/*** 该方法本身很简单,只是单纯把任务放到队列中,指定两个参数** @param runnable 任务内容* @param after    任务在多少毫秒之后执行*/public void schedule(Runnable runnable, long after) {//注意这里时间上的换算MyTask myTask = new MyTask(runnable, System.currentTimeMillis() + after);queue.put(myTask);//只要有新的线程加入就会唤醒waitsynchronized (this) {this.notify();}}
}

测试:

/*** 分别创建三个任务,三个任务的执行时间分别是1000 2000 3000* 如果打印结果分别按照顺序执行则正确*/
public class ThreadTest {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 1000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 3000);}
}

总结:

  1. 这里会按照设定时间的先后打印任务,发现此时打印完三个任务之后程序没有停止,因为在刚刚模拟实现的定时器中采用了while(true)的操作,会一直循环从阻塞队列中获取任务,队列为空就会发生阻塞等待(标准库中的定时器也是想相同的逻辑),默认为前台线程;

  1. 因为采用了优先级队列的数据结构,所以对自己定义的MyTask类一定要设置优先级,需要实现Compareable接口重写compareTo方法;

  1. 这里一定要用wait方法设置一个时间爱参数避免“死等”的现象,从而浪费CPU资源

在schedule方法中设置notify方法来唤醒,每次注册一个任务之后就进行判读时间是否到了是否需要执行


http://www.ppmy.cn/news/11342.html

相关文章

Codeforces Round #841 (Div. 2) (A--D)

[TOC](Codeforces Round #841 (Div. 2)(A–D)) A、 Joey Takes Money 1、题目 2、思路 3、代码 #include<iostream>#include<algorithm>#include<cstring>using namespace std;typedef unsigned long long ll;int main(){ll t;cin>>t;while(t--){int…

ArcGIS基础实验操作100例--实验84查找面到直线的最近点位置

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验84 查找面到直线的最近点位置 目录 一、实验背景 二、实验数据 三、实验步骤 &#…

springAOP的注解使用

注解使用导入依赖常用注解&#xff1a;注意&#xff0c;给测试类起名字的时候千万不要定义成Test&#xff0c;测试的方法不可以有参数&#xff0c;不可以有返回值在使用注解的时候&#xff0c;还需要告诉spring应该从哪个包开始扫描,一般在定义的时候都写上相同包的路径需要导入…

达摩院2023十大科技趋势发布,生成式AI将进入应用爆发期

1月11日&#xff0c;达摩院2023十大科技趋势发布&#xff0c;生成式AI、Chiplet模块化设计封装、全新云计算体系架构等技术入选。达摩院认为&#xff0c;全球科技日趋显现出交叉融合发展的新态势&#xff0c;尤其在信息与通信技术&#xff08;ICT&#xff09;领域酝酿的新裂变&…

RK3399平台开发系列讲解(内核调试篇)如何使用perf进行性能优化

🚀返回专栏总目录 文章目录 一、perf list命令二、perf record/report命令三、perf stat命令四、perf top命令五、火焰图沉淀、分享、成长,让自己和他人都能有所收获!😄 📢perf 可以在 CPU Usage 增高的节点上找到具体的引起 CPU 增高的函数,然后我们就可以有针对性地…

使用ResNet34实现CIFAR100数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用ResNet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到32X32&#xff0c;因为算力相关的…

JAVA实现代码热更新

JAVA实现代码热更新引言类加载器实现热更新思路多种多样的加载来源SPI服务发现机制完整代码类加载器共享空间机制Tomcat如何实现JSP的热更新Spring反向访问用户程序类问题引言 本文将带领大家利用Java的类加载器加SPI服务发现机制实现一个简易的代码热更新工具。 类加载相关知…

C语言常用内存函数的深度解析

文章目录前言memcpymemcpy函数的使用memcpy函数的自我实现memmovememmove函数的使用memmove函数的自我实现memcmpmemcmp函数的使用memcmp函数的自我实现memsetmemset函数的使用memset函数的自我实现写在最后前言 内存函数的使用广泛度大于常用字符串函数的使用广泛度&#xff0…