javaEE->多线程:定时器
一. 定时器
约定一个时间,时间到了,执行某个代码逻辑(进行网络通信时常见)
客户端给服务器发送请求 之后就需要等待 服务器的响应,客户端不可能无限的等,需要一个最大的期限。这里“等待的最大时间”可以用定时器的方式实现。
标准库中有现成的定时器实现:
主线程执行schedule方法的时候,就是把这个任务放到timer对象中。
timer里面本身也含一个线程——>"扫描线程"
时间到了,扫描线程就会执行刚才安排的任务
执行完任务之后,进程并未结束,timer内部的线程阻止了进程的结束。
timer里面也可安排多个任务
public class Demo28 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {System.out.println("执行第四个任务");}},4000);timer.schedule(new TimerTask() {public void run() {System.out.println("执行第三个任务");}},3000);timer.schedule(new TimerTask() {public void run() {System.out.println("执行第二个任务");}},2000);timer.schedule(new TimerTask() {public void run() {System.out.println("执行第一个任务");}},1000);System.out.println("程序启动!");}
}
自己实现一个定时器
1.Timre里面需要有一个线程,扫描任务是否到时间
2.需要有一个数据结构来保存所有的任务
3.需要创建一个类,通过类的对象来描述一个任务。(至少包括任务的内容和时间)
由于任务都带有一个时间的先后顺序,所以我们采用优先级队列的数据结构来实现。
package thread;//自己实现的定时器import java.util.PriorityQueue;//通过一个类来描述任务
class MyTimerTask implements Comparable<MyTimerTask>{//执行的任务private Runnable runnnable;//执行任务的时间private long time;//delay是schedule方法传入的“相对时间”public MyTimerTask(Runnable runnable, long delay) {this.runnnable = runnable;this.time = System.currentTimeMillis()+ delay;}public int compareTo(MyTimerTask o) {//让队首元素是最小时间的值return (int)(this.time-o.time);}public long getTime() {return time;}public Runnable getRunnable() {return runnnable;}
}//定时器的结构
class MyTimer {//通过优先级队列来存储所有的任务,里面的元素务必使可比较的(TreeSet/TreeMap也要求这样)private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//锁对象Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized (locker) {queue.offer(new MyTimerTask(runnable,delay));//添加新的任务就唤醒扫描线程locker.notify();}}//扫描线程public MyTimer() {Thread t = new Thread(() -> {//不停地扫描队首元素,看是否到达时间while(true) {try{synchronized (locker) {while(queue.isEmpty()) {//用循环的话,是为了确认一下唤醒之后是不是真的不为空了//如果队列为空,就需要等待//等待另外的线程添加新的任务唤醒locker.wait();}MyTimerTask task = queue.peek();//队首元素,最早的的任务long curTime = System.currentTimeMillis();//当前的时间if(curTime >= task.getTime()) {//到了时间,该执行任务了task.getRunnable().run();//执行任务之后,可从队列中删除queue.poll();}else {//还未到时间locker.wait(task.getTime() -curTime);//线程还需要等待的时间//如果不加上wait的话,线程就处于忙等,会消耗很多cpu资源//用上wait阻塞之后呢,线程不会在cpu上调度,把资源让给别人}}}catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}
public class Demo29 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("2000");}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("1000");}},1000);}
}
二.线程池
线程诞生的意义:因为进程的创建/销毁,太重量了(比较慢)
但如果近一步提高创建/销毁的频率,线程的开销也不容忽视。
有两种方法可以提高效率:
1.协程(轻量级线程):相对于线程,把系统调度的过程省略了。
使用协程更多的是go和python
不知道协程能提升多少,防止出现bug,java一般使用线程
2.线程池:帮线程兜底,不至于很慢
(内存池、线程池、进程池含义类似)
在使用第一个线程的时候,提前把2,3,4,5(其余)线程创建好;
后续如果想要使用新的线程,不必重新创建,直接调用即可,这样的话创建线程的开销就减少了。
调用线程比创建新线程效率跟高
1.调用线程是纯粹“用户态”的操作
2.创建新的线程 是需要“用户态+内核态”共同完成的
内核态和用户态:
一段程序在系统内核执行 -> 内核态