[Java EE] 多线程(六):线程池与定时器

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (90平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(93平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

1. 线程池

1.1 什么是线程池

我们前面提到,线程的创建要比进程开销小,但是如果线程的创建/销毁比较频繁,开销也会比较大.所以我们便引入了线程池,线程池的作用就是提前把线程都创建好,放到用户态代码中写的数据结构中,后面就可以随用随取.
线程池最大的好处就是减少每次启动,销毁线程的开销.线程的创建和销毁,需要通过用户态+内核态来配合完成,但是线程池只需要通过用户态即可完成,不需要内核态的配合.
在这里插入图片描述
在这里插入图片描述

举例说明:渣女小故事
有一位小姐姐是个渣女,创建和销毁线程就像一位小姐姐和他的男朋友分手了,再和下一位男朋友一点一点培养感情,而线程池就相当于,小姐姐有一个备胎池,他同时和好几个小哥哥培养感情,如果和其中一个分手了,就可以从备胎池中调用其他小哥哥,无缝衔接.
在这里插入图片描述

1.2 线程池的构造方法参数解释(面试常考)

线程池的类名是TreadPoolExecutor,其中他的构造方法中提供了丰富的参数类型,我们下面来一一解释一下这些参数:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
  1. corePoolSize:线程池中的核心线程数.
  2. maximumPoolSize:线程池中的最大线程数.等于核心线程数+非核心线程数,其中非核心线程数是根据当前任务的多少来动态管理的.(至于最大线程要设置为多少,这个没有一个固定的值,一般认为,和CPU的逻辑核心数有关,可能是n+1,2n等)
  3. keepAliveTime: 允许非核心线程数的最大空闲时间.超出最大空闲时间,就会被回收.
  4. unit:等待时间的时间单位,时,分,秒.
  5. workQueue:用于存放任务的阻塞队列.后续线程池内部的工作线程就会消费这个队列,从而完成任务的执行.
  6. threadFactory:线程创建工厂,参与具体的线程创建工作.通过不同的工厂创建出的不同线程相当于对一些属性进行了不同的初始化设置.
    针对线程工厂,我们又可以引出另一个话题:工厂设计模式.
    这种设计模式主要是针对解决构造方法的不足而创建出的一种设计模式.
    比如,我们给出了一个点(Point)类:
    我们知道,一个点想要表示出来,一种是通过笛卡尔坐标系表示,一种是通过极坐标的方式来表示,但是我们会发现,笛卡尔坐标系的参数和极坐标系的参数个数相同,而且它们的参数类型也相同,这就使得我们不可以用构造方法来构造这个点.
public class Point {public double x = 0;public double y = 0;public double r = 0;public double a = 0;public Point(double x, double y) {this.x = x;this.y = y;}public Point(double r,double a){this.a = a;this.r = r;}
}

编译报错:
在这里插入图片描述
所以我们便引入了工厂设计模式来解决这个问题:
我们引入PointBuilder这个类,通过PointBuilder来构造这个点,并在Point这个点中设置set方法.

class Point {private double x = 0;private double y = 0;private double r = 0;private double a = 0;public void setX(double x) {this.x = x;}public void setY(double y) {this.y = y;}public void setR(double r) {this.r = r;}public void setA(double a) {this.a = a;}
}
class PointBuilder{public static Point pointXY(double x,double y){Point p = new Point();p.setX(x);p.setY(y);return p;}public static Point pointRA(double r,double a){Point p = new Point();p.setA(a);p.setR(r);return p;}
}
  1. handler:任务超出线程池的承受范围的拒绝策略
    • AbortPolicy():超过负荷,直接抛出异常.
    • CallerRunsPolicy():调用者负责处理多出来的任务.
    • DiscardOldestPolicy():丢弃队列中最老的任务.
    • DiscardPolicy():丢弃新来的任务.

举例说明:年会不能停
有请老朋友:马杰克,潘妮,杰弗瑞,胡建林
corePoolSize:相当于公司中的正式员工,比如马杰克,杰弗瑞,胡建林.
maximumPoolSize:相当于公司中的所有员工,包括正式员工+外包.
keepAliveTime:允许外包员工的最大空闲时间,比如允许潘妮的最大空闲时间,一旦超过最大空闲时间,杰弗瑞就会把潘妮开除掉.
unit:允许潘妮的空闲时间单位,时,分,秒.
workQueue:每次员工们领取工作任务的账号.
threadFactory:公司不同的HR,通过HR招人(创建线程)
handler:员工们任务太多时候的拒绝策略
比如有一天杰弗瑞需要和胡建林要去干一场直播,但是胡建林由于工作太多,它需要拒绝杰弗瑞,其中胡建林有多种拒绝他的策略.第一种方法就是:胡建林直接罢工,工作和直播都不干了,就是直接罢工了.(AbortPolicy()).第二种方法就是:胡建林让杰弗瑞自己去直播(CallerRunsPolicy()).第三种方法就是:胡建林可以先放下手头的工作,和杰弗瑞去直播(DiscardOldestPolicy()).第四种方法就是:胡建林可以先完成自己的工作,之后在去和杰弗瑞直播(DiscardPolicy())
在这里插入图片描述

1.3 标准库中的线程池

Java源码的编写者也知道,上面这个方法用起来特别难,因为有许多参数.所以Java的标准库中提供了创建线程的一个工厂类Executors(本质上是Executor的封装),其中有一些创建线程的方法.这些方法的返回值可以直接当做线程池来使用,返回值的类型为ExecutorService.

  • 线程池中的核心方法为submit,它是为线程池的阻塞队列中添加Runnable对象.
  • 创建线程的工厂类Executor中右许多创建线程的方法,不同方法返回的线程属性各不相同.
    • newFixedTreadPool(int nTread):创建固定线程数的线程池
    • newCachedTreadPool():创建线程数目动态变化的线程池.(随任务数量变化)
    • newSingleTreadExectuor():创建单线程的线程池.
    • newScheduleTreadPool():设定延迟时间后执行指令,或者定期执行指令,是进阶版的Timer.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo24 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(10);//固定创建10个线程,注意返回类型for (int i = 0; i < 100; i++) {int finalI = i;service.submit(new Runnable() {@Overridepublic void run() {System.out.println("tread"+ finalI + Thread.currentThread().getName());}//只有固定的10个线程在执行这100个任务});}ExecutorService service1 = Executors.newCachedThreadPool();//随着任务的增多,线程创建增多for (int i = 0; i < 100; i++) {int finalI = i;service1.submit(new Runnable() {@Overridepublic void run() {System.out.println("tread"+ finalI + Thread.currentThread().getName());}});}}
}

创建固定线程线程池的运行结果:
在这里插入图片描述
我们看到,submit方法提供了100个任务,但是执行这100个任务的只有10个线程在执行.

下面是创建一个现场数量动态变化的线程池:
在这里插入图片描述
我们看到,线程池中被创建出了很多线程,100个任务由很多线程来执行.

1.4 实现线程池

  • 首先我们要实现核心方法submit,将任务添加到线程池的队列中.
  • 使用Worker类描述一个工作线程.使用Runnable描述一个任务.
  • 使用BlockingQueue组织所有的任务.
  • 每个Worker线程需要做的事情就是不断从BlockingQueue中获取任务并执行.
  • 指定线程池的最大线程数.
  • submit方法时生产者,不断往队列中添加Runnable元素,而构造方法是消费者,不停地从队列中获取Runnable元素并执行.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;/*** 创建固定线程的线程池*/
public class MyTreadPool {public BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//通过阻塞队列来保存可运行对象//向阻塞队列提交任务,生产者public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//构造方法创建线程来执行队列中的任务.消费者public MyTreadPool(int nTread) {for (int i = 0; i < nTread; i++) {Thread thread = new Thread(()->{while (true){//每个线程不停地从队列中取出元素try {queue.take().run();//获取任务清单} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();//创建完线程之后立即启动}}public static void main(String[] args) throws InterruptedException {MyTreadPool myTreadPool = new MyTreadPool(10);for (int i = 0; i < 1000; i++) {int finalI = i;myTreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("Tread"+ finalI + Thread.currentThread().getName());}});}}
}

运行结果:
在这里插入图片描述

2. 定时器

2.1 概念

指定一个任务(Runnable),并且指定一个时间,此时这个任务不会立即执行,而是在时间到达之后再执行.
在这里插入图片描述
定时器在我们日常的开发中也非常常见.比如在双11,0点开始定时抢购,再比如如果网络超过5000ms无响应的时候,就会尝试重新连接等.

2.2 标准库中的定时器

  • 标准库中提供一个Timer类.Timer的核心方法为schedule,为Timer的队列中添加元素.
  • schedule包含两个参数,第一个参数指定将要执行的任务代码,第二个参数指定多长时间执行.
import java.util.Timer;
import java.util.TimerTask;public class Demo25 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},3000);//延迟3s后再执行}
}

2.3 定时器的模拟实现

  • 队列中使用优先级队列实现,把时间作为优先级队列的比较规则,delay时间短的放在堆顶.(注意不要使用PriorityBlockingQueue,容易死锁)
  • 队列中的每一个元素是一个Task对象.
  • Task中带有一个时间属性.
  • 同时有一个Worker线程一直扫描队首元素,通过与当前系统的时间戳去比较,看队首元素是否到达执行时间.
  • 其次,由于我们使用的是优先级队列,其中一定存在线程安全问题,我们需要使用synchronized对其进行加锁.
import java.util.PriorityQueue;/*** 描述一个任务类*/
class TimerTask implements Comparable<TimerTask>{public Runnable runnable;public long time;public TimerTask(Runnable runnable, int delay) {this.runnable = runnable;this.time = System.currentTimeMillis()+delay;}@Overridepublic int compareTo(TimerTask o) {return (int)(this.time-o.time);//这里谁减谁不要记,试一试就知道了}
}
/*** 定时器*/
public class MyTimer {public PriorityQueue<TimerTask> priorityQueue = new PriorityQueue<>();/*** 该方法为优先级队列提供任务,生产者* @param runnable 可运行对象* @param delay 延时时间*/public void schedule(Runnable runnable,int delay){synchronized (this){//添加元素时加锁priorityQueue.offer(new TimerTask(runnable,delay));this.notify();//唤醒构造方法的wait}}/*** 创建线程执行队列中的任务,消费者*/public MyTimer() {Thread thread = new Thread(()->{while (true){//循环扫描堆顶元素,判断是否要执行synchronized (this){//由于优先级队列的不安全性,所以加锁if (priorityQueue.isEmpty()){try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long cur = System.currentTimeMillis();//注意保存当前时间,不可以写在判断时间中//否则每次都在改变TimerTask task = priorityQueue.peek();if (cur >= task.time){priorityQueue.poll().runnable.run();}else {try {this.wait(task.time-cur);//注意等待时间是任务时间减去当前是时间} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});thread.start();//注意启动线程}public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello");}},2000);}
}

注意事项:

  • 不可以把wait()那一行使用continue代替,否者就会出现"忙等"的状态.
  • wait()处不可以用sleep()代替,否者就把锁抱死了,应该在休眠的时候让其他线程继续调度系统资源.

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

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

相关文章

功能描述如何逻辑

assign相当于连线&#xff0c;注意每个assign独立&#xff0c;如果有变化立刻变化

CSS选择器、字体文本属性、三大特性、盒子模型等

目录 导入css简介HTML的局限性CSS-网页美化CSS语法规范CSS代码风格 选择器基础选择器复合选择器 CSS字体属性字体系列字体大小字体粗细文字样式字体复合属性 CSS文本属性文本颜色对齐文本装饰文本文本缩进行间距(即行高) CSS的引入方式emmet语法元素显示模式什么是&#xff1f;…

python:dict(字典、映射)使用解析

列表使用&#xff1a;[] 元组使用&#xff1a;() 字符串使用&#xff1a;"" 集合使用&#xff1a;{} 字典&#xff1a;{key:value} 生活中的字典&#xff1a;记录大量的字与含义&#xff1b; python中的字典&#xff1a;通过key去找到对应的value;[key]:value …

Vue 工程化开发入门

Vue开发的两种方式&#xff1a; 核心包传统开发模式&#xff1a;基于html/css/js文件&#xff0c;直接引入核心包&#xff0c;开发Vue工程化开发模式&#xff1a;基于构建工具的环境中开发Vue 这里选择Vue cli脚手架 进行开发&#xff0c;搜索教程自行下载。 组件化开发 一个页…

STM32入门学习之ADC

1.ADC在STM32进行数据采集时十分重要。通过ADC可以将外界的数字信号转换为模拟信号&#xff0c;以满足采样的需求。(资料参考于正点原子) STM32 拥有 1~3 个 ADC &#xff08; STM32F101/102 系列只有 1 个 ADC &#xff09;&#xff0c;这些 ADC 可以独立使用&#…

苍穹外卖,接入redis cache后,新增套餐有问题

终端报错&#xff1a; java.lang.IllegalArgumentException: Null key returned for cache operation (maybe you are using named params on classes without debug info?) Builder[public com.sky.result.Result com.sky.controller.admin.SetmealController.save(com.sky.d…

虚拟机网络实现桥接模式

虚拟机网络实现桥接模式 虚拟化软件&#xff1a;VMware 17 Linux&#xff1a;rocky8_9 主机&#xff1a;Win10 文章目录 虚拟机网络实现桥接模式1. 桥接模式介绍2. 查看Win本机的网络信息&#xff08;以笔记本电脑以WiFi联网为例&#x…

保姆级教程申请地理位置接口都给我去试

小程序地理位置接口有什么功能&#xff1f; 目前小程序的地理位置接口已经调整为审核制了&#xff0c;也就是说我们开发者如果小程序需要用到getlocation等接口的话&#xff0c;需要先在小程序后台进行开通申请&#xff0c;提交相关证明材料才可以获得接口使用权限。 小程序地理…

Fourier 测试时间自适应与多级一致性用于鲁棒分类

文章目录 Fourier Test-Time Adaptation with Multi-level Consistency for Robust Classification摘要方法实验结果 Fourier Test-Time Adaptation with Multi-level Consistency for Robust Classification 摘要 该研究提出了一种名为 Fourier 测试时间适应&#xff08;FTT…

汽车车灯的材料是什么?汽车车灯的灯罩如果破损破裂破洞了要怎么修复?

汽车车灯的材料主要包括灯罩和灯底座两部分&#xff0c;它们所使用的材料各不相同。 车灯罩的材料主要是透明且具有良好耐热性和耐紫外线性能的塑料。其中&#xff0c;聚碳酸酯&#xff08;PC&#xff09;是一种常用的材料&#xff0c;它具有高抗冲击性、耐化学品腐蚀和优良的…

大数据BI可视化(Echarts组件)项目开发-熟悉结合Vue开发图表组件7.0附带1/6商家销售统计(横向柱状图)

一.创建项目 创建 1.npm install -g vue/cli vue create vision 2. 3. 4.版本 5.是否使用历史路由 6.CSS预处理 7.ES标准配置 8.啥时候es标准提示-保存文件后 9.将配置文件放入单独文件中处理 10.需要保留新建项目以上设置 11.选择“Use PNPM”或者“Use NPM” 12.创建 13访…

五一假期零碎时间练习学习过的内容(商城版)

目录 1 总览1.1 技术架构1.2 其他1.2.1 数据库1.2.2 后端部分1.2.2.1 复习feign1.2.2.2 复习下网关网关的核心功能特性&#xff1a;网关路由的流程断言工厂过滤器工厂全局过滤器 过滤器执行顺序解决跨域问题 1.2.2.3 es部分复习 1.2.3 前端部分 2 day1 配置网关2.1 任务2.2 网关…

【4088】基于小程序实现的二手物品交易系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

pandas学习笔记11

DataFrame结构 DataFrame 一个表格型的数据结构&#xff0c;既有行标签&#xff08;index&#xff09;&#xff0c;又有列标签&#xff08;columns&#xff09;&#xff0c;它也被称异构数据表&#xff0c;所谓异构&#xff0c;指的是表格中每列的数据类型可以不同&#xff0c;…

目标检测算法YOLOv5简介

没有关于YOLOv5的直接论文&#xff0c;YOLOv5由Ultralytics维护&#xff0c;源码见&#xff1a;https://github.com/ultralytics/yolov5 &#xff0c;于2020年6月发布v1.0版本&#xff0c;最新发布版本为v7.0&#xff0c;License为AGPL-3.0. 以下内容主要来自&#xff1a; 1. U…

2024.4.29 Pandas day01 基础语法

pandas是python的一个数据库&#xff0c;在使用数据库的时候需要输入 import pandas as pd 引入&#xff0c; df pd.read.csv(文件路径“&#xff09;&#xff1a;这是利用pandas数据库读取CSV文件的方法&#xff0c;如果读取EXCEL文件或者其他文件&#xff0c;csv文件换成其他…

库存管理系统开源啦

软件介绍 ModernWMS是一个针对小型物流仓储供应链流程的开源库存管理系统。该系统的开发初衷是为了满足中小型企业在有限IT预算下对仓储管理的需求。通过总结多年ERP系统研发经验&#xff0c;项目团队开发了这套适用于中小型企业的系统&#xff0c;以帮助那些有特定需求的用户。…

Python-Socket编程实现tcp-udp通信

本文章是记录我准备大创项目时学的socket编程的用法&#xff0c;纯属记录生活&#xff0c;没有教学意义&#xff0c;视频我是看b站up主王铭东学的&#xff0c;讲的很详细&#xff0c;我只粗略学了个大概&#xff0c;我想要通过tcp&#xff0c;udp传输yolo目标检测中的物体坐标信…

C语言中的goto语句

goto label; C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。 #include <stdio.h> int main(){goto first;printf("我是你好\n");first:printf("nihao\n");second:printf("This is 2\n");return 0; } 使用goto会…

== 和 equals()区别,equals()重写问题

对于引用类型&#xff1a;比较的是两个引用是否相同&#xff08;所指的是否为同一个对象&#xff09;&#xff0c;注&#xff1a;如果两个引用所指的对象内容一样&#xff0c;但是不是同一个对象&#xff08;hashcode不一样&#xff09;&#xff0c;依然返回false&#xff0c;随…