赞
踩
定时器像是一个闹钟,在一定时间之后,被唤醒并执行某个之前设定好的任务。
之前学习的 join(指定超时时间)
sleep(休眠指定时间)
都是基于系统内部的定时器,来实现的。
标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule 。
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)
public class Demo23 {
public static void main(String[] args) {
Timer timer =new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello timer");
}
},3000);
System.out.println("main");
}
}
执行效果
Timer内部需要什么东西?
1)描述任务
创建一个专门的类来表示一个定时器中的任务(TimerTask)
//创建一个类,表示一个任务 class MyTask{ //任务具体要干什么 private Runnable runnable; //任务具体什么时候干,保存任务要执行的毫秒级时间戳 private long time; public MyTask(Runnable runnable, long after) { this.runnable = runnable; this.time = System.currentTimeMillis()+after; } public void run(){ runnable.run(); } }
2)组织任务(使用一定的数据结构把一些任务给放到一起)
class MyTimer{
//定时器内部要能存放多个任务
private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay){
MyTask task=new MyTask(runnable, delay);
queue.put(task);
}
}
3)执行时间到了的任务
需要先执行时间在靠前的任务
就需要一个线程,不停的去检查当前优先级队列的队首元素,看看说当前最靠前的这个任务是不是时间到了。
//提供一个MyTimer的构造方法 public MyTimer(){ Thread t=new Thread(() ->{ while(true){ try { //先取出队首元素 MyTask task=queue.take(); //在比较一下看看当前这个任务时间到了没 long curTime=System.currentTimeMillis(); if(curTime < task.getTime()){ //时间没到,把任务在放到队列中 queue.put(task); }else{ //时间到了,执行任务 task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); }
上述代码存在严重缺陷!!!
第一个缺陷:MyTask没有指定比较规则
MyTask这个类的比较规则,并不是默认就存在的,这个需要手动指定,按照时间大小来比较的。
Java标准库中的集合类,很多都有一定的约束限制的,不是随便拿个类都能放到这些集合类里面去的
第二个缺陷
可以基于wait这样的机制来实现
wait有个版本,指定等待时间(不需要notify,时间到了自然唤醒)。
计算出当前时间和任务目标之间 的时间差,就等待这么长时间。
那么既然是指定一个等待时间,,为什么不直接使用sleep呢?而是要用wait?
sleep不能被中途唤醒
wait能够被中途唤醒
在等待过程中,可能要插入新的任务,新的任务是可能出现在之前所有任务的最前面的,在schedule操作中,就需要加上一个notify操作。
因此最终的代码是:
//创建一个类,表示一个任务 class MyTask implements Comparable<MyTask>{ //任务具体要干什么 private Runnable runnable; //任务具体什么时候干,保存任务要执行的毫秒级时间戳 private long time; public MyTask(Runnable runnable, long delay) { this.runnable = runnable; this.time = System.currentTimeMillis()+delay; } public void run(){ runnable.run(); } public long getTime(){ return time; } @Override public int compareTo(MyTask o) { return (int) (this.time-o.time); } } class MyTimer{ //定时器内部要能存放多个任务 private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>(); public void schedule(Runnable runnable,long delay){ MyTask task=new MyTask(runnable, delay); queue.put(task); //每次任务插入成功之后,都唤醒一下扫描线程,让线程重新检查一下队首元素的任务是否时间到要执行 synchronized (locker){ locker.notify(); } } private Object locker=new Object(); //提供一个MyTimer的构造方法 public MyTimer(){ Thread t=new Thread(() ->{ while(true){ try { //先取出队首元素 MyTask task=queue.take(); //在比较一下看看当前这个任务时间到了没 long curTime=System.currentTimeMillis(); if(curTime < task.getTime()){ //时间没到,把任务在放到队列中 //指定一个等待时间 synchronized (locker){ locker.wait(task.getTime()-curTime); } queue.put(task); }else{ //时间到了,执行任务 task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } } public class Demo24 { public static void main(String[] args) { MyTimer timer=new MyTimer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("hello timer"); } },3000); System.out.println("hello main"); } }
进程,比较重,频繁创建销毁,开销大,解决方案:进程池或线程。
线程,虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是有的,解决方案:线程池或协程。
把线程提前创建好,放到池子里,后面需要使用线程,直接从池子里取,就不必从系统这边申请了。线程用完,也不是还给系统,而是放回池子里,以备下次再用。
这回创建销毁过程,速度就更快了。
为什么认为线程放到池子里,就比从系统这边申请释放更快呢??
有一个程序,这个程序要并发的 / 多线程的来完成一些任务,如果使用线程池的话,这里的线程数设为多少合适?
正确的做法:要通过性能测试的方式,找到合适的值
例如:写一个服务器程序,服务器里通过线程池,多线程的处理用户请求。
就可以对这个服务器进行性能测试,比如构造一些请求,发送给服务器,要测试性能,这里的请求就需要构造很多,比如每秒发送500 / 1000/2000…,根据实际的业务场景,构造一个合适的值。
根据这里不同的线程池的线程数,来观察,程序处理任务的速度,程序持有的CPU的占用率。
当线程数多了,整体的速度会快,CPU占用率也会高
当线程数少了,整体的速度会慢,CPU占用率也会下降。
需要找到一个让程序速度能接受,并且CPU占用率也合理这样的平衡点。
不同类型的程序,因为单个任务,里面的CPU上计算的时间和阻塞的时间分布是不同的。因此,只说一个数字是不靠谱的。
搞多线程,就是为了让程序跑的更快,那为什么考虑不让CPU占用率太高?
对于线上服务器来说,要留有一定的冗余,随时应对一些可能的突发情况(例如,请求突然暴涨)
如果本身已经把CPU快占完了,这时候突然来一波请求的峰值,此时服务器可能直接就挂了。
标准库中还提供了一个简化版本的线程池
Executors
本质是针对ThreadPollExecutor进行封装,提供了一些默认参数。
public class Demo25 { public static void main(String[] args) { //创建一个固定线程数目的线程池,参数指定了线程个数 ExecutorService pool=Executors.newFixedThreadPool(10); //创建一个自动扩容的线程池,会根据任务自动进行扩容 //Executors.newCachedThreadPool(); //创建一个只有一个线程的线程池 // Executors.newSingleThreadExecutor(); //创建一个带有定时器功能的线程池,类似于Timer // Executors.newScheduledThreadPool(); pool.submit(new Runnable() { @Override public void run() { System.out.println("hello threadpool"); } }); } }
线程池中有什么?
1.先能够描述任务(直接使用Runnable)
2.需要组织任务(直接使用BlockingQueue)
3.能够描述工作线程
4.还需要组织这些线程
5.需要往线程池里添加任务
class MyThreadPool{ //1.描述一个任务,直接使用Runnable,不需要额外创建类了 //2.使用一个数据结构来组织若干个任务 private BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>(); //3.描述一个线程,工作线程的功能就是从任务队列中取任务并执行 static class Worker extends Thread{ //当前线程池中有若干个worker线程,这些线程内部,都持有了上述的任务队列 private BlockingDeque<Runnable> queue=null; public Worker(BlockingDeque<Runnable> queue){ this.queue=queue; } @Override public void run() { while(true){ //就需要能够拿到上面的队列 try { //循环的获取任务队列中的任务 //这里如果队列为空,就直接阻塞,如果队列不为空,就获取到里面的内容 Runnable runnable=queue.take(); //获取到之后,就执行任务 runnable.run(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //创建哪一个数据结构来组织若干个线程 private List<Thread> workers=new ArrayList<>(); public MyThreadPool(int n){ //在构造方法中,创建若干个线程,放到上述的数组中 for(int i=0;i<n;i++){ Worker worker=new Worker(queue); worker.start(); workers.add(worker); } } //创建一个方法,能够让程序员来放任务到线程池 public void submit(Runnable runnable){ try { queue.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Demo26 { public static void main(String[] args) { MyThreadPool pool=new MyThreadPool(10); for(int i=0;i<100;i++){ pool.submit(new Runnable() { @Override public void run() { System.out.println("hello thraedpool"); } }); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。