【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…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...