定时器与线程池

文章目录

  • 定时器
    • 定时器的工作原理
    • 定时器的使用
    • 实现定时器
  • 线程池
    • 线程池存的优点
    • 线程池的使用
    • 线程池的原理
      • 工厂模式
      • ThreadPoolExecutor类
    • 实现线程池

定时器

定时器也是软件开发中的一个重要组件,我们可以将一个任务交给定时器,约定好时间到了定时器就执行该任务。

比如,当客户端发出请求后,会等待服务器的响应,但是由于网络环境的复杂性,如果吃吃没有得到响应,也不会一直等待下去(不现实),此时就会设定一个最大等待时间,这里的这个最大等待时间,就可以使用定时器的方式来实现,当时间到达最大等待时间后,就放弃等待。

定时器的工作原理

在标准库中提供了定时器Timer类,Timer 类的核心方法为 schedule(),
schedule() 包含两个参数,第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。

定时器会将传入的任务和与其相对应的等待时间存放到带有阻塞的优先级队列(线程安全)中,每次只需要维护时间最短的任务,时间没到时就可以进入阻塞,当时间间隔最短的任务到了执行时间,就对其进行出队操作,并执行任务里面的内容。

定时器的使用

  public static void main(String[] args) {System.out.println("程序启动");Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器任务1");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器任务2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器任务3");}},1000);}

执行结果~~

程序启动
执行定时器任务3
执行定时器任务2
执行定时器任务1

我们可以看到,定时器按照我们指定的任务和时间进行工作了,但是任务执行完毕,线程并不会结束,而是会进入阻塞,等待下一个任务的到来。

实现定时器

定时器是通过优先级队列来维护待执行的任务及其对应的时间,并且我们要实现compareTo接口。


class Task implements Comparable<MyTask>{private Runnable runnable;// 任务 - 交给线程执行private long time;// 等待时间public Task(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}//获取任务的时间public long getTime() {return time;}//执行任务public void run() {runnable.run();}@Overridepublic int compareTo(Task o) { // 重写compare接口return (int)(this.time - o.time);}
}

定时器中有一个扫描线程扫描优先级队列中(优先级最高的)的任务是否到了执行时间。

不断将队(优先级最高的)首元素取出,判断是否到达时间,然候根据情况选择是将任务放回还是执行,这个需要一个循环去不停的执行。

但是如果任务的执行时间和当前时间相差很多,不断的取出、判断、放回 会额外占用很多cpu资源(线程忙等),所以我们对其考虑进行一个睡眠操作,睡眠的时间就是 – 任务要执行的时间减去当前的时间,但是,如果有新的任务传了进来,并且时间比当前的最短时间要短就可能出现新任务没有被执行的情况,所以,上面的睡眠操作是不可取的,而是应该使用wait,在wait中设置最大的等待时间,当有新的任务传进来时将wait唤醒,然后重新判断,具体实现如下:

class MyTimer {//扫描线程private Thread t = null;private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(() -> {while(true) {try {synchronized(this) {Task myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {queue.put(myTask);this.wait(myTask.getTime() - curTime);} else {myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}public void schedule(Runnable runnable,long after) {//时间戳需要进行换算Task myTask = new Task(runnable,System.currentTimeMillis() + after);queue.put(myTask);synchronized(this) {this.notify();}}
}

一定要注意加锁的范围,锁一定要包括扫描线程while里面的全部内容,因为如果扫描线程计算完需要等待的时间之后wait之前,扫描线程被切走,此时有一个新任务传了进来,执行了notify之后扫描线程才开始进行工作,那么扫描线程就没有扫描到新的任务,如果新的任务的时间更短,那么新的任务就没有被执行。

完整代码


class Task implements Comparable<Task>{private Runnable runnable;private long time;public Task(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}//获取任务的时间public long getTime() {return time;}//执行任务public void run() {runnable.run();}@Overridepublic int compareTo(Task o) {return (int)(this.time - o.time);}
}class MyTimer {//扫描线程private Thread t = null;private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(() -> {while(true) {try {synchronized(this) {Task myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {queue.put(myTask);this.wait(myTask.getTime() - curTime);} else {myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}public void schedule(Runnable runnable,long after) {//时间戳需要进行换算Task myTask = new Task(runnable,System.currentTimeMillis() + after);queue.put(myTask);synchronized(this) {this.notify();}}
}

线程池

线程池存的优点

线程是系统调度的最小单位,由于进程的开销太大,就引入了线程,在java中更喜欢使用多线程。除了线程还有一种“协程”也可以进一步提高性能,但是有于标准库并没有很好的支持,在java中使用的并不算多。

但由于对性能的进一步追求,此时就引入了线程池的技术,池化技术是一种非常重要的思想,使用非常广泛,比如,线程池、内存池、常量池、连接池等。

我们可以提前(一次性)创建一些线程,加入线程池中,在需要使用线程时,直接去线程池中取,不用时也不着急释放。这样由于是一次性创建(内核态+ 用户态),之后从线程池中拿取(户态操作),就会提降低程序的开销。此时申请和释放的”度“可以由程序员去自己设计,而不用全部听从系统内核的调度,使得程序更加的可控。

线程池的使用

常见的线程池创建有四种:

//可以设置线程数量
ExecutorService pool = Executors.newFixedThreadPool(10);
//根据任务的数量 去动态变化线程的数量
ExecutorService pool = Executors.newCachedThreadPool();
//只有一个线程
ExecutorService pool = Executors.newSingleThreadExecutor();
//类似定时器,让任务延时进行
ExecutorService pool = Executors.newScheduledThreadPool(10);

使用的方法很简单,就是调用里面的submit方法,在里面传一个任务就可以了,如下:创建1000个任务让线程池执行

public static void main(String[] args) {//	设置线程数量ExecutorService pool = Executors.newFixedThreadPool(10);for(int i = 0;i < 1000;i++) {int n = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello" + n);}});}
}

线程池的原理

工厂模式

    线程池的创建时没有使用new的,而是调用了一个方法,而真正的new操作在方法里面进行,这样的设计模式叫做工厂模式。

    工厂模式作用是什么呢?在java中,重载的规则是在 方法名(可以省略,因为重载主要就是需要方法名相同)、参数个数、参数类型 其中至少有一项不同,否则就无法达成重载。
    那么如果有两种构造方法,他们想要构成重载,但是又达不到重载的条件,此时,就可以使用工厂模式,去根据不同的需求去构造。

    举个例子,假如有一个类,它的用途是构造出一个坐标系上的点,构造这样的点可以传入x、y坐标,也可以传入距原点的半径长和角度大小r、a(极坐标),这四个参数都需要double类型,数量相同,方法名也相同,无法达成重载,此时就可以使用工厂模式去创建

Point point1 = newXYPoint(1,1);
Point point2 = newRAPoint(1,1);

ThreadPoolExecutor类

线程池的四种构造方法都是对ThreadPoolExecutor的封装,我们只需要了解ThreaPoolExecutoe类即可。

ThreadPoolExecutor类有7个参数。

  1. int corePoolSize

    • 核心线程数,也就是线程池中固定的线程数量
  2. int maximumPoolSize

    • 最大线程数,是线程池中可以包含的最大线程数量

最大线程数和核心线程数的差值属于临时线程,也就是可以允许被回收掉的线程,比如当前任务量很多,那么就可以多创建几个临时线程去执行任务,当任务量比较少的时候,这些临时线程没有事情干就可以被回收掉。

  1. long keepAliveTime 和 TimeUnit unit(时间单位:s、ms、分钟…)

    • 这两个参数描述了临时线程可以最长的“闲置”时间,如果临时线程在一定的时间内没有工作,那么此时就会被回收掉。
  2. BlockingQueue workQueue

    • 线程池的任务队列,用一个阻塞队列来 接收、取出 任务。
  3. ThreadFactory threadFactory

    • 线程池的工厂方法,用于创建线程。
  4. RejectedExecutionHandler handler

    • 线程池的拒绝策略。

    标准库中提供了四种线程池的拒绝策略,如下:
在这里插入图片描述

  1. AbortPolicy - 队列满了,触发异常(摆烂了 - 罢工!!!)
  2. CallerRunsPolicy - 队列满了,就将该任务交还给加入线程执行(谁给我的谁执行)
  3. DiscardOldestPolicy - 队列满了,丢弃最早的任务,将该任务加入队列
  4. DiscardPolicy - 队列满了,丢弃最新任务,将该任务加入队列。

实现线程池

    简易线程池的实现:

class MyPool {private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();public MyPool(int n) {for(int i = 0;i < n;i++) {Thread t = new Thread(() -> {while(true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}public void submit(Runnable runnable) {try {queue.put(runnable);} catch (InterruptedException e) {e.printStackTrace();}}
}
public class PoolDemo2 {public static void main(String[] args) {MyPool myPool = new MyPool(10);for(int i = 0;i< 1000;i++) {int n = i;myPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello " + n);}});}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/225481.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

纯干货篇,用Stable diffusion制作广告的保姆级教程

今天就想给大家演示一般如何使用Stable diffusion为你的产品添加场景&#xff0c;秒变广告大片。 掌握这个技能&#xff0c;你可以随意将产品添加不同的场景&#xff0c;节约复杂的拍摄布景和拍摄成本。 话不多说&#xff0c;接下来是详细讲解演示流程 首先选出一张你的产品图…

解决方案:新版WPS-右键粘贴值到可见单元格没有了

WPS筛序后复制&#xff0c;并且粘贴到可见单元格 &#xff08;如果直接粘贴数据会乱掉&#xff09; 旧版WPS&#xff0c;右键就能出现 但是新版WPS不是在这里&#xff08;方法1&#xff09; 新版WPS&#xff08;方法2&#xff09; 视频详细教程链接&#xff1a;解决方案&…

抽象工厂模式-C语言实现

说明&#xff1a; 均由 chatgpt生成&#xff0c;实例可以看出无法运行&#xff0c;仅供参考~ 抽象工厂模式&#xff1a; 代码实现&#xff1a; #include <stdio.h>// 定义抽象产品接口 typedef struct {void (*operation)(); } AbstractProductA;typedef struct {voi…

淼一科技为互联网企业销毁硬盘数据 拆除机房设备

在上海这座繁华的大都市&#xff0c;淼一科技以其专业的服务和卓越的技术&#xff0c;为众多互联网企业提供硬盘数据销毁和机房设备拆除服务。作为业界领先的数据安全解决方案提供商&#xff0c;淼一科技致力于保障客户数据的安全与隐私&#xff0c;为客户创造更高的商业价值。…

Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)

目录 Node.js Stream(流)&#xff08;三&#xff09; Node.js http模块 Node.js GET/POST请求&#xff08;一&#xff09; Node.js GET/POST请求&#xff08;二&#xff09; Node.js 路由 Node.js 创建客户端 Node.js 作为中间层 Node.js 文件系统模块&#xff08;一&am…

解释LED显示屏的裸眼3D特效原理

LED电子大屏幕的3D特效技术正在不断发展&#xff0c;而实现这一技术的原理主要包括分光、分色、分时和光栅等四种方法。这些原理都有各自的特点和应用场景&#xff0c;下面将对它们进行详细介绍。 1. 分光方法 分光方法是一种基于偏振光的3D显示技术。通过使用偏振滤镜或偏振片…

曝光!WPS用户信息或被盗用,紧急行动,迅软DSE数据加密应时而动!

WPS摊上大事了&#xff01;有用户发现&#xff0c;在WPS更新的一版用户隐私政策中&#xff0c;明确提到&#xff1a;“我们将对您主动上传的文档材料&#xff0c;在处理后作为AI训练的基础材料使用。”换句话说&#xff0c;WPS有可能“白嫖”用户的文档信息&#xff0c;用于投喂…

A preview error may have occurred. Switch to the Log tab to view details.

我们在学习鸿蒙应用开发的UIAbility内页面间的跳转内容的时候会遇到页面无法跳转的问题。并伴随标题的error报错 Entry Component struct Index {build() {Column({ space: CommonConstants.COLUMN_SPACE }) {//UIAbility内页面间的跳转Button(Next).onClick(() > {router.…

好用的基于layui的免费开源后台模版layuimini

发现一个好用的后台模版 基于layui的免费开源后台模版layuimini layuimini - 基于Layui的后台管理系统前端模板 easyadmin开源项目 EasyAdmin是基于ThinkPHP6.0Layui的快速开发的后台管理系统。 演示站点 演示地址&#xff1a;跳转提示&#xff08;账号&#xff1a;admin&a…

【mmseg】ValueError: Only one of `max_epochs` or `max_iters` can be set.报错解决

目录 &#x1f49c;&#x1f49c;1背景 ❤️ ❤️2分析 &#x1f525;2.1config查看 &#x1f525;2.2BaseRunner基类 &#x1f49a;&#x1f49a;3解决 &#x1f525;3.1按照epoch &#x1f525;3.2按照iters 整理不易&#xff0c;欢迎一键三连&#xff01;&#xff01…

PTA NeuDS-数据库题目集

一.判断题 1.在数据库中产生数据不一致的根本原因是冗余。T 解析&#xff1a;数据冗余是数据库中产生数据不一致的根本原因&#xff0c;因为当同一数据存储在多个位置时&#xff0c;如果其中一个位置的数据被修改&#xff0c;其他位置的数据就不一致了。因此&#xff0c;在数据…

在Linux中对Docker中的服务设置自启动

先在Linux中安装docker&#xff0c;然后对docker中的服务设置自启动。 安装docker 第一步&#xff0c;卸载旧版本docker。 若系统中已安装旧版本docker&#xff0c;则需要卸载旧版本docker以及与旧版本docker相关的依赖项。 命令&#xff1a;yum -y remove docker docker-c…