当前位置:网站首页>实现定时器
实现定时器
2022-07-30 06:39:00 【Master_hl】
目录
1.定时器是什么
1.定时器是一种实际开发中非常常用的组件.2.比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.3.比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).4.类似于这样的场景就需要用到定时器.
2.标准库中的定时器
public class TestDemo1 {
public static void main(String[] args) throws InterruptedException {
// java.util 里的一个组件
Timer timer = new Timer();
// schedule : 安排一个任务
// 该任务不是立刻执行,而是延迟多少时间后再执行!!
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("这是一个要执行的任务!");
}
},3000);
// 定时器 和 sleep 不相同
while(true) {
System.out.println("main");
Thread.sleep(1000);
}
}
}【注意事项】
1.定时器的核心方法是 schedule, schedule 包含两个参数,第一个参数指定即将要执行的任务,第二个参数指定多长时间后执行(单位:毫秒)。
2.将定时器和 sleep 做区分,sleep 是使当前线程处于阻塞状态,而 定时器 只是记录了多长时间后该执行的任务,中间的这些时间,当前线程该干嘛就干嘛。
3.schedule 里面的 TimerTask 其实就相当于 Runnable,只不过是 TimerTask 实现了 Runnable 接口,在这里我们直接把它当成 Runnable 就好了。
3.实现定时器
通过观察标准库中的定时器,我们大概知道要怎么做了!!
1. Timer 内部要组织很多的任务;
2. Timer 里的每个任务都要通过一定的方式来描述出来;(自己定义一个 TimerTask)
3. 还需要有一个线程,通过这个线程来扫描定时器内部的任务,执行其中时间到了的任务。(Timer 内部的线程)
【代码实现】
class MyTask implements Comparable<MyTask>{
// 任务
private Runnable command;
// 任务开始执行的时间(相对时间)
private long time;
public MyTask(Runnable command, long after) {
this.command = command;
// 绝对时间戳
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 Runnable 的 run 方法即可
public void run() {
command.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
//自己创建的定时器类
class MyTimer {
// 用来阻塞等待的锁对象
private Object locker = new Object();
// 使用优先级阻塞队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command : 要执行的任务是啥
// after : 任务啥时候执行
public void schedule(Runnable command, long after) {
MyTask myTask = new MyTask(command, after);
synchronized (locker) {
queue.put(myTask);
locker.notify();
}
}
public MyTimer() {
//在这里启动一个线程
Thread t = new Thread(() -> {
while(true) {
//循环过程中,就不断的尝试从队列中获取到队首元素
//判断队首元素当前的时间是否就绪,如果就绪了就执行,不就绪,就不执行
try {
synchronized (locker) {
//取出队首任务
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(myTask.getTime() > curTime) {
//时间还没到,就放回去
queue.put(myTask);
//等待最早的任务开始的时间和当前时间的差值
locker.wait(myTask.getTime() - curTime);
} else {
//时间到了,就执行任务
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}【分析实现过程中的重点步骤】
重点 1:使用优先级阻塞队列将我们的任务组织起来!!
1.虽然任务可能有很多,但是它们的执行顺序是一定的,且按照时间顺序先后来执行的,所以使用优先级队列。
2.在多线程环境下,这个队列会被多个线程访问,第一,schedule 可能是在多线程中被调用,每次调用都要往队里添加元素;第二,定时器内部还需要有专门的线程来执行队列里的任务。这些操作在多线程里都是存在线程安全问题的,所以需要使用到优先级阻塞队列!!
重点2:优先级阻塞队列里的元素是一个引用类型(MyTask),所以我们需要指定比较规则,既可以让 MyTask 实现 Comparable 接口,也可以在优先级阻塞队列的构造方法中传参,传一个比较器 Comparator !!
重点3:执行任务的时候:队列不为空时,我们先将元素取出来,判断是否到了执行时间,没到时间就放回去,放回去之后要加上 wait() 。那么加入新任务的时候也就相应的要 notify() ,避免让 CPU 出现"空转"的现象!!

重点4:我们的加锁不能只包裹 wait() ,notify(),否则会出现非原子性操作,从而导致线程安全问题!!

经过上述分析,我们发现多线程的代码真的是防不胜防,稍微一点不注意,都可能引起线程安全问题!!
边栏推荐
猜你喜欢

Delphi仿制Web的导航
Get all interface paths and names in the controller

Go uses the mencached cache

C# 使用RestSharp 实现Get,Post 请求(2)

2020 ACM | MoFlow: An Invertible Flow Model for Generating Molecular Graphs

SOFA Weekly|Meetup 广州站、本周 QA、本周 Contributor

golang grpc protoc 环境配置

Selected as one of the "Top Ten Hard Core Technologies", explaining the technical points of Trusted Confidential Computing (TECC) in detail

selenium模块

用代码收集每天热点内容信息,并发送到自己的邮箱
随机推荐
interface
谷粒商城--环境部署(2022/7/28最新)
Keil compile size and storage instructions
MYSQL 主从恢复锁表后, 处理SQL 线程锁解决.
Input method for programmers
潜心打磨,主动求变——这群技术排头兵,如何做好底层开发这件事?
go : go-redis set operations
使用navicat连接mysql数据库时常报的错误:2003、1698、1251
适合程序员的输入法
Develop common tool software
General Lei's personal blog to see
MySQL basics [naming convention]
docker部署redis一主二从三哨兵模式
golang: Gorm configures Mysql multiple data sources
防止资源导出失败
「活动推荐」探索未来:数字科技
How to calculate the daily cumulative capital flow one by one in real time
IDEA search plug-in has no results and the solution has been spinning in circles
Common configuration
MySQL基础篇【命名规范】