【JavaEE】定时器
目录
前言
什么是定时器
如何使用java中的定时器
实现计时器
实现MyTimeTask类
Time类中存储任务的数据结构
实现Timer中的schedule方法
实现MyTimer中的构造方法
处理构造方法中出现的线程安全问题
完整代码
考虑在限时等待wait中能否用sleep替换
能否用PriorityBlockingQueue进行存储

在前面,已经讲解了几种常见设计模式,那么今天我们就来讲解一下定时器。
前言
在发送信息的时候,有时候不想要信息那么快就发送出去,而是在特定的时间再发送;或者我们在发送邮件时,当达到特定的时间时,就会自动发送电子邮件给用户,那么这里就需要用到定时器,那么定时器是什么呢?
什么是定时器
定时器是软件开发中的一个重要组件,类似于“闹钟”,能够在某个特定的时间执行一个或者多个任务,定时器是多线程中的一个案例,也是一个比较复杂且重要的案例。

如何使用java中的定时器
在java中,给我们提供了实现了的定时器包,我们可以直接使用。
java中给我们提供的定时器是Timer,我们在设置定时任务时,需要用到其中的schedule方法。

schedule包含两个参数:
第⼀个参数指定即将要执⾏的任务代码
第⼆个参数指定多⻓时间之后 执⾏(单位为毫秒)
示例:
class Demos{public static void main(String[] args) {// 创建一个Timer对象,用于调度定时任务Timer timer=new Timer();// 调度第一个定时任务,1秒后执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},1000);// 调度第二个定时任务,2秒后执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},2000);// 调度第三个定时任务,3秒后执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},3000);}}
实现计时器
我们从上述代码中可以看出,要实现一个定时器,需要实现以下:
- 实现一个任务(Task)类
- 实现一个Timer类用来存放任务
实现MyTimeTask类
/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask{//需要执行的任务private Runnable runnable;//任务等待执行的时间点,以毫秒为单位private long time;/*** 构造函数,用于创建一个TimeTask对象* @param runnable 需要延迟执行的任务,类型为Runnable* @param delay 任务延迟执行的时间,单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnable=runnable;//计算任务应该执行的时间点this.time=System.currentTimeMillis()+delay;}/*** 返回任务的等待执行时间* @return 任务等待执行的时间点,以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}
}
Time类中存储任务的数据结构
我们在存储任务的时候,需要根据等待时间来存储,时间短的优先取出来,那么我们就可以使用优先级队列,创建一个小根堆,时间最短的放在堆顶。
private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>();
但是我们这里要怎么比较呢?我们可以实现Comparable接口重写compareTo方法来进行比较,或者创建一个类来实现Comparator接口重写compare方法来进行比较。
实现Timer中的schedule方法
当我们解决了在任务在优先级队列中如何进行比较存储任务的问题之后,那么就可以在MyTimer中来实现schedule方法。根据schedule方法的参数创建一个MyTimeTask类,并将其添加到优先级队列中。
/*** 将一个 Runnable 任务安排在指定的延迟时间后执行* * @param runnable 要执行的任务* @param delay 相对于现在的时间延迟,单位为毫秒* * 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行*/
public void schedule(Runnable runnable, long delay) {// 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行pq.add(myTimeTask);
}
实现MyTimer中的构造方法
通过实例化一个线程,在这个线程中,通过多次扫描优先级队列中的元素,判断堆顶元素是否到达了等待时长,若是,则取出并执行。
注意:这里不能直接poll取出栈顶元素,若栈顶的任务等待时间还未到达,则继续循环。
/*** 构造函数,初始化MyTimer对象* 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread t=new Thread(()->{// 无限循环,持续检查任务队列while(true) {if(pq.isEmpty()){continue;}// 查看在栈顶的任务MyTimeTask task = pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() >= task.getTime()) {// 当前时间已达到任务设定时间,移除任务并执行pq.poll();task.run();}else {// 如果栈顶的任务时间大于当前时间,则继续循环// 继续检查下一个任务,或在没有到达时间的任务时继续等待continue;}}});// 启动线程t.start();}
处理构造方法中出现的线程安全问题
在上述代码中,能看出哪里存在线程安全问题吗?
优先级队列并不是一个线程安全的队列,我们在瞥(peek)取(poll)的时候,可能会出现线程安全问题,若是在多线程环境中,当线程1刚peek了堆顶任务,但此时切换到线程2,线程2同样peek堆顶任务,并刚好到了等待时间,此时就会执行并且删除栈顶任务。此时又切换到线程1,但此时线程1peek的堆顶任务已经被poll掉了,此时如果再执行,就会再次删除堆顶任务,导致出现线程安全问题。
所以,这里我们需要对peek和poll操作进行加锁。
/*** 构造函数,初始化MyTimer对象* 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread t=new Thread(()->{// 无限循环,持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空,则跳过当前循环if (pq.isEmpty()) {continue;}// 查看在栈顶的任务MyTimeTask task = pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() >= task.getTime()) {// 当前时间已达到任务设定时间,移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间,则继续循环// 继续检查下一个任务,或在没有到达时间的任务时继续等待continue;}}}});// 启动线程t.start();}
这里还有什么能优化的吗?
我们可以看到,当优先级队列中不为空,但此时堆顶任务的等待时间还没到,此时就会进入else分支执行continue,但一直重复这样操作,可能会造成不断检查,cpu使用率过高。那么我们就可以使用带参数的wait来进行限时等待,当达到时限时,会自动唤醒线程。
同理的,在判断队列是否为空时,我们可以设置不带参数的wait,等待唤醒。
/*** 构造函数,初始化MyTimer对象* 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread t=new Thread(()->{// 无限循环,持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空,则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task = pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() >= task.getTime()) {// 当前时间已达到任务设定时间,移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间,则继续循环try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}
既然这里等待,那么我们就需要有人来唤醒wait,所以我们在schedule方法中也需要进行加锁,并且在添加完任务后,调用notify来进行通知。
/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** @param runnable 要执行的任务* @param delay 相对于现在的时间延迟,单位为毫秒** 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行pq.add(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}
MyTimer优化到这里,其实已经优化好了。
完整代码
package Threads;import java.util.Comparator;
import java.util.PriorityQueue;/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask implements Comparable<TimeTask>{//需要执行的任务private Runnable runnable;//任务等待执行的时间点,以毫秒为单位private long time;/*** 构造函数,用于创建一个TimeTask对象* @param runnable 需要延迟执行的任务,类型为Runnable* @param delay 任务延迟执行的时间,单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnable=runnable;//计算任务应该执行的时间点this.time=System.currentTimeMillis()+delay;}/*** 返回任务的等待执行时间* @return 任务等待执行的时间点,以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}@Overridepublic int compareTo(TimeTask o) {return (int) (this.time- o.getTime());}
}
class MyTimer{//private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>(new compareTimeTask());private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>();static Object lock=new Object();/*** 构造函数,初始化MyTimer对象* 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread t=new Thread(()->{// 无限循环,持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空,则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task = pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() >= task.getTime()) {// 当前时间已达到任务设定时间,移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间,则继续循环try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** @param runnable 要执行的任务* @param delay 相对于现在的时间延迟,单位为毫秒** 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行pq.add(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}}
class Demo{public static void main(String[] args) {MyTimer timer=new MyTimer();timer.schedule(()->{System.out.println("Hello World1");},1000);timer.schedule(()->{System.out.println("Hello World2");},2000);timer.schedule(()->{System.out.println("Hello World3");},3000);}
}
class compareTimeTask implements Comparator<MyTimeTask>{@Overridepublic int compare(MyTimeTask o1, MyTimeTask o2) {return (int) (o1.getTime()-o2.getTime());}
}
测试一下

考虑在限时等待wait中能否用sleep替换

Thread.sleep(task.getTime()-System.currentTimeMillis());
这里为什么不用sleep呢?
在前面线程安全问题中已经讲解了wait和sleep的区别,在这里,如果我们使用sleep,会导致拉着锁一起进入睡眠,导致其他线程拿不到锁对象,无法进行加锁。
这会导致我们想要调用schedule方法添加任务时,拿不到锁对象。
能否用PriorityBlockingQueue进行存储
在前面,我们用的是优先级队列PriorityBlockingQueue来存储任务,但如果我们用PriorityBlockingQueue呢?
如果我们使用PriorityBlockingQueue,那么我们的方法也需要改成take(取)和put(存),才能用阻塞等待的效果。
修改代码:
package Threads;import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask implements Comparable<TimeTask>{//需要执行的任务private Runnable runnable;//任务等待执行的时间点,以毫秒为单位private long time;/*** 构造函数,用于创建一个TimeTask对象* @param runnable 需要延迟执行的任务,类型为Runnable* @param delay 任务延迟执行的时间,单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnable=runnable;//计算任务应该执行的时间点this.time=System.currentTimeMillis()+delay;}/*** 返回任务的等待执行时间* @return 任务等待执行的时间点,以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}@Overridepublic int compareTo(TimeTask o) {return (int) (this.time- o.getTime());}
}
class MyTimer{//private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>(new compareTimeTask());private PriorityBlockingQueue<MyTimeTask> pq=new PriorityBlockingQueue<>(100);static Object lock=new Object();/*** 构造函数,初始化MyTimer对象* 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread t=new Thread(()->{// 无限循环,持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空,则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task = null;try {task = pq.take();} catch (InterruptedException e) {throw new RuntimeException(e);}// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() >= task.getTime()) {// 当前时间已达到任务设定时间,移除任务并执行task.run();} else {// 如果栈顶的任务时间大于当前时间,则继续循环pq.put(task);try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** @param runnable 要执行的任务* @param delay 相对于现在的时间延迟,单位为毫秒** 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行pq.put(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}}
class Demo{public static void main(String[] args) {MyTimer timer=new MyTimer();timer.schedule(()->{System.out.println("Hello World1");},1000);timer.schedule(()->{System.out.println("Hello World2");},2000);timer.schedule(()->{System.out.println("Hello World3");},3000);}
}
class compareTimeTask implements Comparator<MyTimeTask>{@Overridepublic int compare(MyTimeTask o1, MyTimeTask o2) {return (int) (o1.getTime()-o2.getTime());}
}
由于take会触发阻塞等待,而后面的wait也会,这里加了两次锁,容易引出线程安全问题,所以我们建议使用一个锁对象lock,来进行加锁就行。而不使用无界阻塞队列。

以上就是本篇所有内容~
若有不足,欢迎指正~
相关文章:
【JavaEE】定时器
目录 前言 什么是定时器 如何使用java中的定时器 实现计时器 实现MyTimeTask类 Time类中存储任务的数据结构 实现Timer中的schedule方法 实现MyTimer中的构造方法 处理构造方法中出现的线程安全问题 完整代码 考虑在限时等待wait中能否用sleep替换 能否用PriorityBlo…...
2024带你轻松玩转Parallels Desktop19虚拟机!让你在Mac电脑上运行Windows系统
大家好,今天我要给大家安利一款神奇的软件——Parallels Desktop 19虚拟机。这款软件不仅可以让你在Mac电脑上运行Windows系统,还能轻松切换两个操作系统之间的文件和应用程序,让你的工作效率翻倍! 让我来介绍一下Parallels Desk…...
【算法】递归实现二分查找(优化)以及非递归实现二分查找
递归实现二分查找 思路分析 1.首先确定该数组中间的下标 mid (left right) / 2; 2.然后让需要查找的数 findVal 和 arr[mid] 比较 findVal > arr[mid],说明要查找的数在 arr[mid] 右边,需要向右递归findVal < arr[mid],说明要查…...
CDN 是什么?
CDN是一种分布式网络服务,通过将内容存储在分布式的服务器上,使用户可以从距离较近的服务器获取所需的内容,从而加速互联网上的内容传输。 就近访问:CDN 在全球范围内部署了多个服务器节点,用户的请求会被路由到距离最…...
索引:SpringCloudAlibaba分布式组件全部框架笔记
索引:SpringCloudAlibaba分布式组件全部框架笔记 一推荐一套分布式微服务的版本管理父工程pom模板:Springcloud、SpringCloudAlibaba、Springboot二SpringBoot、SpringCloud、SpringCloudAlibaba等各种组件的版本匹配图:三SpringBoot 3.x.x版…...
2024第五届华数杯数学建模竞赛C题思路+代码
目录 原题背景背景分析 问题一原题思路Step1:数据读取与处理Step2:计算最高评分(Best Score, BS)Step3:统计各城市的最高评分(BS)景点数量 程序读取数据数据预处理 问题二原题思路Step1: 定义评价指标Step2: 收集数据Step3: 标准化…...
FFmpeg源码:av_reduce函数分析
AVRational结构体和其相关的函数分析: FFmpeg有理数相关的源码:AVRational结构体和其相关的函数分析 FFmpeg源码:av_reduce函数分析 一、av_reduce函数的声明 av_reduce函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0…...
nginx: [error] open() “/run/nginx.pid“ failed (2: No such file or directory)
今天 准备访问下Nginx服务,但是 启动时出现如下报错:(80端口被占用,没有找到nginx.pid文件) 解决思路: 1、 查看下排查下nginx服务 #确认下nginx状态 ps -ef|grep nginx systemctl status nginx#查看端口…...
<数据集>BDD100K人车识别数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:15807张 标注数量(xml文件个数):15807 标注数量(txt文件个数):15807 标注类别数:7 标注类别名称: [pedestrian, car, bus, rider, motorcycle, truck, bicycle] 序号…...
利用SSE打造极简web聊天室
在B/S场景中,通常我们前端主动访问后端可以使用axios,效果很理想,而后端要访问前端则不能这样操作了,可以考虑SSE、websocket等方式,实时和性能均有保障。 下面给出一个简单的SSE例子,后端是nodeexpress&am…...
代码随想录第二十天|动态规划(4)
目录 LeetCode 322. 零钱兑换 LeetCode 279. 完全平方数 LeetCode 139. 单词拆分 总结 LeetCode 322. 零钱兑换 题目链接:LeetCode 322. 零钱兑换 思想:首先定义dp数组的含义,dp[j]即总金额为j的情况下所需的最少的硬币个数。接下来确定…...
TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急
目录 TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急 一、TreeSize介绍 二、下载安装TreeSize 2.1、下载地址 2.2、下载步骤 2.3、安装步骤 三、professional版的TreeSize试用 3.1、分析磁盘空间 3.2、显示拓展名统计信息 3.3、显…...
如何建立自己的技术知识体系
已经工作五年了,慢慢的觉得不能再继续像以前一样,工作中用到啥才去学啥,平时积累的知识也是非常的零碎,我现在要做的就是建立自己的技术知识体系。 我感觉学习技术知识就想是探索一个城市一样,技术知识体系就好比是这…...
JS优化了4个自定义倒计时
<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>4个自定义倒计时</title><style>* {margin: 0;padding: 0;box-sizing: border-box;user-select: none;body {background: #0b1b2c;}}hea…...
模型实战(25)之 基于LoFTR深度学习匹配算法实现图像拼接
模型实战(25)之 基于LoFTR深度学习匹配算法实现图像拼接 图像拼接在全景图、大图或者多目场景下经常会被使用,常用的方法有传统图像处理算法和深度学习直接获取对应点的算法传统图像处理算法过程繁琐,阈值少且整体算法结果对调参比较敏感,其主要通过形状、特征点等描述子对…...
计算机毕业设计Python+Spark知识图谱高考志愿推荐系统 高考数据分析 高考可视化 高考大数据 大数据毕业设计
《Spark高考推荐系统》开题报告 一、选题背景及意义 1. 选题背景 随着我国高考制度的不断完善和大数据技术的飞速发展,高考志愿填报已成为考生和家长高度关注的重要环节。传统的志愿填报方式依赖于考生和家长手动查找和对比各种信息,不仅效率低下且容…...
【python】文件
在python中可以通过文件操作,将数据保存到计算机硬盘中文件,可以包含文本数据,也可以包含二进制数据(图片,视频,音频等)。 目录 前言 正文 一、基本语法 1、函数open()打开file 返回一个文件对象 1.1、文件路径 1&a…...
《Attention Is All You Need》核心观点及概念
这个文件据说是一篇很厉害的AI论文,https://arxiv.org/pdf/1706.03762 这篇论文《Attention Is All You Need》确实是AI领域中的一个里程碑,它改变了我们处理语言的方式。 下面小编会用简单的语言来解释这篇文章的核心观点和学术概念,并告诉大家它为什么很厉害。 核心观点…...
【中项】系统集成项目管理工程师-第9章 项目管理概论-9.9价值交付系统
前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…...
JS+H5美观的带搜索的博客文章列表(可搜索多个参数)
实现 美观的界面(电脑、手机端界面正常使用)多参数搜索(文章标题,文章简介,文章发布时间等)文章链接跳转 效果图 手机端 电脑端 搜索实现 搜索功能实现解释 定义文章数据: 文章数据保存在一个 JavaScri…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
