提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、定时器
代码中的定时器,通常都是设定多长时间之后执行某个动作
应用:客户端,服务器开发,客户端设置一个超时时间,等待服务器相应
1.标准库中的定时器
schedule方法(安排任务)参数有俩个
import java.util.Timer;
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("时间到,快起床");
}
},3000);
类似于runnable,要安排的任务是啥,就是一个runnable,咱们要做的是继承timerTask,然后重写run方法, 从而在指定要执行的任务
delat经过多长时间执行
一个定时器可以同时安排多个任务,这里的线程是前台线程,进程不会退出
代码演示
import java.util.Timer;
import java.util.TimerTask;
//演示标准库中的定时器
public class demo24 {
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("时间到,快起床");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("时间到2!");
}
},4000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("时间到3!");
}
},5000);
System.out.println("开始计时");
}
}
2.自己实现的定时器
schedule第一个参数是任务,需要能够描述这个任务,任务包含俩个信息,一个是执行啥工作,一个是啥时候执行
用mytimer来管理多个任务,timer可以安排多个任务
很明显,设置的时间越短的任务,越要先执行,如果使用arraylist,元素无序,不方便找到时间最小的任务 考虑优先阻塞队列来实现
private BlockingQueue<Mytask> queue = new PriorityBlockingQueue<>();
schdule可能在多线程中调用,使用blockingqueue实现
任务已经安排在优先级阻塞队列当中,接下来就需要从队列中取元素了,创建一个单独的扫描线程,让这个线程不停的检查队首元素,时间是否到了,如果时间到了就执行任务
由于阻塞队列,无法阻塞取对首元素 因此就需要先取出任务,然后才能够判断任务时间是否到了,如果任务时间没到,还得把任务塞回去。
4.运行时出现异常:
需要自定义一个comparable方法来选择比较方式
class Mytask implements Comparable<Mytask>{
@Override
public int compareTo(Mytask o) {
return (int) (this.time - o.time);
}
}
错误代码
5.此时定时器还有一个严重的问题:
纯纯的浪费cpu资源,一直在忙等,不断的询问时间,进行判断比较,cpu并没有被空闲出来
错误解决方案1:
给等待的过程加锁,当有任务安排时,尝试唤醒wait,重新分配任务,但是
当put线程竞争到锁,安排一个更早的任务,schedule执行完毕,执行notify,回到扫描线程
由于take操作不是原子的,导致没进行到wait时候,curtime是旧任务时间,导致空打一炮(虽然通知了,但没有唤醒任何人),没有及时唤醒wait,发现当前任务时间依然是旧的时间,错过了任务
错误写法二:
错误原因:会造成死锁,扫描线程取队首元素时候,发现队列为空,阻塞等待(需要另一个线程安排任务才能唤醒),由于扫描线程上了锁,此时schdule线程无法拿到锁,就会一直阻塞等待
正确代码:
正确做法(锁的粒度加大)
刚才出现问题的原因,就是notify在take和wait之间执行的,现在把扫描线程中的锁范围放大了,可以避免notify在take和wait之间执行了,扫描线程会先拿到锁,然后take,中间逻辑,一直到wait,在这个过程中,schedule会阻塞等待,直到扫描线程执行了wait,扫描线程释放了锁,schdule拿到锁,进行通知,这个时候wait被唤醒,下次重新取队首元素,就会把1:00执行的任务取出来了
代码演示:
package Threading;
import java.util.ArrayDeque;
import java.util.PriorityQueue;
import java.util.TimerTask;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
//自己实现一个定时器
class Mytask implements Comparable<Mytask> {
//要执行的任务
private Runnable runnable;
//什么时间来执行任务(是一个时间戳)
private long time;
public Mytask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = time;
}
public Runnable getRunnable() {
return runnable;
}
public long getTime() {
return time;
}
@Override
public int compareTo(Mytask o) {
return (int) (this.time - o.time);
}
}
class MyTimer {
private BlockingQueue<Mytask> queue = new PriorityBlockingQueue<>();
private Object locker = new Object();
public MyTimer() {
//扫描一个线程
Thread t = new Thread(() -> {
while (true) {
try {
synchronized (locker) {
//取出队首元素
Mytask task = queue.take();
//假设当前时间是2:30,任务设定时间是2:30,显示就要执行任务了
//假设当前时间是2:30,任务设定时间是2:29,也是到点了,也要执行任务
long curTime = System.currentTimeMillis();
if (curTime >= task.getTime()) {
//到点了,该执行任务了
task.getRunnable().run();
} else {
//还没到点
queue.put(task);
locker.wait(task.getTime() - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
public void schedule(Runnable runnable, long after) throws InterruptedException {
Mytask mytask = new Mytask(runnable, after);
queue.put(mytask);
synchronized (locker) {
locker.notify();
}
}
}
public class demo25 {
public static void main(String[] args) throws InterruptedException {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("时间到1!");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("时间到2!");
}
}, 4000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("时间到3!");
}
}, 5000);
System.out.println("开始计时");
// ArrayDeque<String> a=new ArrayDeque<>();
// a.peekLast();
}
}