定时器 Timer(超详细模拟实现)

目录

一、定时器

1.定时器概述

2.Java标准库提供的定时器类

3.定时器代码样例

二、实现

1.实现思路

2.代码实现

2.1纯享版

2.2注释版

3.代码解析(超详细)

3.1描述类MyTimerTask

①构造:MyTimerTask(Runnable runnable,  long delay)

②排序:compareTo(MyTimerTask  o)

③另两个

3.2※定时器类MyTimer

①任务队列入队:schedule(Runnable runnable, long delay)

②扫描线程|构造: MyTimer()

③线程安全问题处理

4.一般执行流程分析


 

5ed82899d812479d89597547c2e1431a.png

一、定时器

1.定时器概述

定时器作为软件开发中的重要组件,用于在特定的时间间隔内执行某些任务。类似于我们生活中的“闹钟”,在关键时刻提醒我们开始工作。当然,程序可不像人类一样有惰性,该出手时就出手,可不会拖拖拉拉,能够阻止它的只有bug或它自己。

它的应用场景,例如,在网络通信中,如果需要在500ms内没有收到数据时断开连接并尝试重新连接,就需要使用定时器。另外,定时器还可以用于定期执行一些维护任务,例如清理缓存、备份数据等。

2.Java标准库提供的定时器类

java.util.Timer是Java提供的定时器标准类,使用它可以在指定时间后执行某个任务。而TimerTask类则是保存这份任务的载体,它存储任务的代码及执行的时间,实现了Runnable接口。

Timer类的核心方法是schedule,有两个参数,一个参数指定即将要执行的任务代码,第二个参数指定多长时间后执行(单位为毫秒)。

方法参数1参数2

public void schedule(TimerTask task, long delay)

将要执行的任务代码

指定多长时间后执行

(单位为毫秒)

        //创建Timer对象Timer timer = new Timer();//设置三秒后输出"Hello timer!!"timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Hello timer!!");}}, 3000);

Timer对象实例化(new)后需要手动停止,否则Timer创建的前台线程会持续工作,直到调用cancel方法后结束工作。

方法参数

public void cancel()

f57f6366cda44ad68de69147c82e5ffa.png

3.定时器代码样例

多任务差时测试

import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo3 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000ms Hello timer!!");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000ms Hello timer!!");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000ms Hello timer!!");}}, 1000);}
}

55b1196424964a659b4fbfadf6d49408.png

二、模拟实现

1.实现思路

参考标准库中的定时器实现,有如下构成:

  • 优先级队列,按执行时间循序存储任务
  • 一个schedul方法进行入队
  • 一个task类描述任务
  • 一个持续运转的线程,扫描到时间的任务并执行
  • 其他属性和方法,如时间time属性,构造方法

就像是一个业务窗口(扫描线程),预约的人(任务)排成队伍(优先级队列),随时有其他人根据预约时间(执行时间)先后插入(入队)其中,业务窗口一直面对的都是其中最先预约的人(队首),而且还要等到人预约时间到了才办理相应的业务(执行任务)。

b734a6af0ba248b7a6e0b3e552cf8584.png

根据如上结构我们分别要实现两个类,MyTimer和MyTimerTask。

MyTimer在调用构造方法时就要将扫描线程创建出来,不断判断优先级队列中的队首任务是否到了执行时间,到了就执行任务,若优先级队列为空,则阻塞等待入队方法schedule的调用。

MyTack用于描述任务,需要time属性记录执行时间,runnable对象存储任务(runnable重写run方法),同时,想要成为优先级队列的成员必须是可比较的,因此要实现Comparable接口和compareTo方法,构造方法所需属性由MyTimer类的入队方法schedule提供。

2.代码实现

2.1纯享版

import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;class MyTimerTask implements Comparable<MyTimerTask> {private long time;private Runnable runnable = null;//构造方法public MyTimerTask(Runnable runnable, long delay) {this.time = System.currentTimeMillis() + delay;this.runnable = runnable;}//执行方法,执行runnable存的任务public void run() {runnable.run();}//用于获取任务执行时间public long getTime() {return this.time;}@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time - o.time);}
}class MyTimer {private Thread thread = null;private Object lock = new Object();private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//构造方法public MyTimer() {thread = new Thread(() -> {while(true) {try {synchronized(lock) {if(queue.isEmpty()) {lock.wait();}MyTimerTask task = queue.peek();long time = System.currentTimeMillis();while(time < task.getTime()) {lock.wait(task.getTime() - System.currentTimeMillis());time = System.currentTimeMillis();task = queue.peek();}queue.poll();task.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}//入队方法,一个参数指定即将要执行的任务代码,第二个参数指定多长时间后执行(单位为毫秒)public void schedule(Runnable runnable, long delay) {synchronized(lock) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);lock.notify();}}
}

2.2注释版

import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;class MyTimerTask implements Comparable<MyTimerTask> {//执行时间private long time;//执行的任务private Runnable runnable = null;//构造方法,参数delay提供的是’相对时间‘,runnable提供的是任务public MyTimerTask(Runnable runnable, long delay) {//执行时间 = 当前时间 + 绝对时间this.time = System.currentTimeMillis() + delay;this.runnable = runnable;}//执行方法,执行runnable存的任务public void run() {runnable.run();}//用于获取任务执行时间public long getTime() {return this.time;}//用于比较执行顺序@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time - o.time);}
}class MyTimer {//扫描线程,用于扫描任务队列,执行到点的任务private Thread thread = null;//锁private Object lock = new Object();//优先级队列,任务队列,对任务按执行时间进行排序private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//构造方法public MyTimer() {//构造方法中创建线程,构造后使扫描线程直接开工thread = new Thread(() -> {//while循环持续扫描while(true) {try {//加锁,保障线程安全synchronized(lock) {//当队列为空时阻塞线程,入队操作执行后解除阻塞if(queue.isEmpty()) {lock.wait();}//获取队首任务,获取当前时间MyTimerTask task = queue.peek();long time = System.currentTimeMillis();//当前时间不到任务执行时间时阻塞线程,等待while(time < task.getTime()) {//设置极限等待时间,到时间自动解除阻塞lock.wait(task.getTime() - System.currentTimeMillis());//等待期间可能又有任务入队,且成为新的队首//而入队操作会解除阻塞,这时候要重置time和队首数据,再通过循环判断当前是否要执行time = System.currentTimeMillis();task = queue.peek();}//执行任务,并从任务队列取出已执行任务queue.poll();task.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}//入队方法,一个参数指定即将要执行的任务代码,第二个参数指定多长时间后执行(单位为毫秒)public void schedule(Runnable runnable, long delay) {synchronized(lock) {MyTimerTask task = new MyTimerTask(runnable, delay);//入队queue.offer(task);//入队时为扫描线程解除阻塞lock.notify();}}
}

3.代码解析(超详细)

3.1描述类MyTimerTask

再看一眼MyTask的要求:MyTack用于描述任务,需要time属性记录执行时间,runnable对象存储任务(runnable重写run方法),同时,想要成为优先级队列的成员必须是可比较的,因此要实现Comparable接口和compareTo方法,构造方法所需属性由MyTimer类的入队方法schedule提供。

一共需要两个变量,long类型time用于记录执行任务的时间,Runnable类对象runnable存储任务。

e8a0a84c80d84613bfc8e3159efd49eb.png

四个方法,构造方法MyTimerTask用于初始化time和runnable,compareTo重写方法用于比较,run方法来执行任务,getTime方法用于外界获取任务执行时间来判断是否执行。

①构造:MyTimerTask(Runnable runnable,  long delay)

没什么好说的,干脆利落的将参数赋给对应的成员变量。

参数方面,考虑到代码实用性,所以传的时相对时间,也就是多少ms后执行任务,因此赋值给time时才要加上当前时间。

    //构造方法,参数delay提供的是’相对时间‘,runnable提供的是任务public MyTimerTask(Runnable runnable, long delay) {//执行时间 = 当前时间 + 绝对时间this.time = System.currentTimeMillis() + delay;this.runnable = runnable;}

②排序:compareTo(MyTimerTask  o)

“return this.time - o.time” 意为时间较time大者调用compareTo返回正值,反之返回赋值,相等则返回0(不明白的话可以复习一下).

这样重写是为了让任务队列队首是time最小,即最先要执行的那个任务。

    //用于比较执行顺序@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time - o.time);}

③另两个

 没必要再提,略……

3.2※定时器类MyTimer

MyTimer在调用构造方法时就要将扫描线程创建出来,不断判断优先级队列中的队首任务是否到了执行时间,到了就执行任务,若优先级队列为空,则阻塞等待入队方法schedule的调用。

MyTimer使用的是优先级队列,且要在全局都访问到,所以要定义成全局变量。锁lock同理。

    //扫描线程,用于扫描任务队列,执行到点的任务private Thread thread = null;//锁private Object lock = new Object();//优先级队列,任务队列,对任务按执行时间进行排序private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

剩下就是两个核心方法,构造方法,以及任务队列入队方法。

①任务队列入队:schedule(Runnable runnable, long delay)

先从相对简单多的schedule方法开始。

逻辑很简单,根据参数实例化任务描述类(task),并将其插入任务队列(优先级队列queue)中,优先级队列会将插入的task按照执行时间从近到远排序,方便后续执行任务。最后,若扫描线程(thread)陷入阻塞,即之前队列为空时或还未到任务执行时间时。

注意,插入后队首可能会发生变化,notify操作又会解除阻塞,可能导致误判出现bug。

    //入队方法,一个参数指定即将要执行的任务代码,第二个参数指定多长时间后执行(单位为毫秒)public void schedule(Runnable runnable, long delay) {synchronized(lock) {MyTimerTask task = new MyTimerTask(runnable, delay);//入队queue.offer(task);//入队时为扫描线程解除阻塞lock.notify();}}

②扫描线程|构造: MyTimer()

预期效果:不断扫描任务队列,当队首任务可执行时自动执行;不可执行时阻塞等待,到点自动执行;队列为空时线程阻塞等待。要避免“忙等”浪费资源。

首先,thread扫描线程在构造第一时间就要创建,开始“扫描”避免错漏或耽搁,代码逻辑要在线程内部实现。

    //构造方法public MyTimer() {//构造方法中创建线程,构造后使扫描线程直接开工thread = new Thread(() -> {//while循环持续扫描while(true) {try {//加锁,保障线程安全synchronized(lock) {//当队列为空时阻塞线程,入队操作执行后解除阻塞if(queue.isEmpty()) {lock.wait();}//获取队首任务,获取当前时间MyTimerTask task = queue.peek();long time = System.currentTimeMillis();//当前时间不到任务执行时间时阻塞线程,等待while(time < task.getTime()) {//设置极限等待时间,到时间自动解除阻塞lock.wait(task.getTime() - System.currentTimeMillis());//等待期间可能又有任务入队,且成为新的队首//而入队操作会解除阻塞,这时候要重置time和队首数据,再通过循环判断当前是否要执行time = System.currentTimeMillis();task = queue.peek();}//执行任务,并从任务队列取出已执行任务queue.poll();task.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}

因为代码过长且不美观,我们将thread内每次循环的代码逻辑抠出来分析:

    synchronized(lock) {//1.当队列为空时阻塞线程,入队操作执行后解除阻塞if(queue.isEmpty()) {lock.wait();}//2.获取队首任务,获取当前时间MyTimerTask task = queue.peek();long time = System.currentTimeMillis();//3.当前时间不到任务执行时间时阻塞线程,等待while(time < task.getTime()) {//设置极限等待时间,到时间自动解除阻塞lock.wait(task.getTime() - System.currentTimeMillis());//等待期间可能又有任务入队,且成为新的队首//而入队操作会解除阻塞,这时候要重置time和队首数据,再通过循环判断当前是否要执行time = System.currentTimeMillis();task = queue.peek();}//4.执行任务,并从任务队列取出已执行任务queue.poll();task.run();}
  1. 首先,判断队列是否为空,为空则阻塞。(为空说明当前无任务要执行,阻塞释放锁,一方面可以节省系统资源,另一方面不释放锁没法进行入队操作添加任务,进而永久阻塞)

  2. 获取队首和当前时间,用变量单独存储。(一方面减少代码冗余,另一方面为下面while循环判断做准备)
  3. while循环判断当前队首任务是否可以执行,不可以则阻塞。(直接使用wait()阻塞不好使用notify解锁,但加上自动解除阻塞时间后就不需要在考虑额外notify的问题了。还有,重置time和task是为了避免阻塞结束后队首发生变化,而当前时间是一定变的。)
  4. 到达执行时间,通过task调用run()执行任务,并把执行过的任务从队列中剔除掉。(这个任务一定是首先要被执行的那个,队首最小。)

外层while循环不断循环上述逻辑就达到了我们所有的预期效果,不断扫描,执行可执行任务。线程安全问题解决具体请往下看。

③线程安全问题处理

MyTimer类的实现是可能出现一些线程安全问题的,那又是如何将这些问题解决的呢。

大部常规问题解决:

读写操作一同出现时很容易出现线程安全问题,上述MiTimer的代码实现中两个方法都同时涉及到了读写操作,如schedule方法中涉及到了queue,而PriorityQueue类型并非是线程安全的。

解决方法也很简单将两个方法涉及到queue的部分都用synchronized包裹起来。

schedule方法调用导致队首变化问题解决:

当MyTimer()代码逻辑执行到“时间不到任务执行时间时阻塞线程”时线程阻塞,在等待期间可能schedule方法会被多次调用,其中可能会出现改变queue队首的情况。

配合schedule中notify的解除阻塞操作,在判断条件是“time < task.getTime()”的情况下,如果task和time(尤其是task)不发生变化就会导致循环判断的其实不是队首这个应该最先执行的任务。

        //当前时间不到任务执行时间时阻塞线程,等待while(time < task.getTime()) {//设置极限等待时间,到时间自动解除阻塞lock.wait(task.getTime() - System.currentTimeMillis());//等待期间可能又有任务入队,且成为新的队首//而入队操作会解除阻塞,这时候要重置time和队首数据,再通过循环判断当前是否要执行time = System.currentTimeMillis();task = queue.peek();}

在阻塞后重置task和time就很好的解决了这一安全问题。

4.一般执行流程实例

public class ThreadDemo {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000ms Hello timer!!");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000ms Hello timer!!");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000ms Hello timer!!");}}, 1000);}
}

实例化MyTimer,扫描线程启动。

MyTimer timer = new MyTimer();

调用schedule方法添加任务,实例化对应的任务对象并插入,优先级队列自动排序如下。

5161fd549c6d4c36af5334b9b85e830e.png

        timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000ms Hello timer!!");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000ms Hello timer!!");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000ms Hello timer!!");}}, 1000);

扫描线程不间断扫描,开始时(实列化MyTimer时)由于队列为空而阻塞,入队添加任务后解除阻塞。

判断队首是否需要执行,由于1000ms后执行,所以暂时阻塞至一秒后自动解除阻塞。

判断通过出队,执行任务,打印"1000ms Hello timer!!"

同理,1000ms后,打印"2000ms Hello timer!!"

又1000ms后,打印"3000ms Hello timer!!"

    public MyTimer() {thread = new Thread(() -> {while(true) {try {synchronized(lock) {if(queue.isEmpty()) {lock.wait();}MyTimerTask task = queue.peek();long time = System.currentTimeMillis();while(time < task.getTime()) {lock.wait(task.getTime() - System.currentTimeMillis());time = System.currentTimeMillis();task = queue.peek();}queue.poll();task.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}

任务队列清空,扫描线程阻塞,等待下次调用schedule方法解除阻塞……

55b1196424964a659b4fbfadf6d49408.png

博主是Java新人,每位同志的支持都会给博主莫大的动力,如果有任何疑问,或者发现了任何错误,都欢迎大家在评论区交流“ψ(`∇´)ψ

0043e0ffc2fa494c8ba6ea472ed59f70.png

 

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

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

相关文章

如何使用本地私有NuGet服务器

写在前面 上一篇介绍了如何在本地搭建一个NuGet服务器&#xff0c; 本文将介绍如何使用本地私有NuGet服务器。 操作步骤 1.新建一个.Net类库项目 2.打包类库 操作后会生成一个.nupkg文件&#xff0c;当然也可以用dotnet pack命令来执行打包。 3.推送至本地NuGet服务器 打开命…

《计算机网络简易速速上手小册》第6章:网络性能优化(2024 最新版)

文章目录 6.1 带宽管理与 QoS - 让你的网络不再拥堵6.1.1 基础知识6.1.2 重点案例&#xff1a;提高远程办公的视频会议质量实现步骤环境准备Python 脚本示例注意事项 6.1.3 拓展案例1&#xff1a;智能家居系统的网络优化实现思路Python 脚本示例 6.1.4 拓展案例2&#xff1a;提…

计算机网络_1.5 计算机网络的性能指标

1.5 计算机网络的性能指标 一、总览二、常用的八个计算机网络性能指标1、速率&#xff08;1&#xff09;数据量&#xff08;2&#xff09;速率&#xff08;3&#xff09;数据量与速率中K、M、G、T的数值辨析&#xff08;4&#xff09;【练习1】计算发送数据块的所需时间 2、带宽…

Unity引擎学习笔记之【角色动画器操作】

角色动画Character Animation 一、使用方法 1. 添加一个静态的角色模型&#xff0c;并确保这个角色模型绑定了Avatar 2. 创建一个动画器控制器 Animator Controller 3. 将动画器控制器拖动添加到Player的Animator控制器上 4. 双击即可打开控制器 5. 将带有动画的预设&#xf…

docker下nacos(1.2.0)的持久化

一、创建数据库 运行以下代码自动创建数据库和表 CREATE DATABASE IF NOT EXISTS nacos_config /*!40100 DEFAULT CHARACTER SET utf8 */; USE nacos_config;SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for config_…

[Python] 如何在控制台进行输入输出

可莉今天带你来学习Python基础中在控制台的输入输出~ 接下来跟着可莉来学习吧~ 可莉将这篇博客收录在了&#xff1a;《Python》 可莉推荐的博主主页&#xff1a;Keven ’ s blog 目录 通过控制台输出 格式化输出 通过控制台输入 总结 通过控制台输出 我们通过print函数可以…

[VulnHub靶机渗透] WestWild 1.1

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

leetcode正则表达式匹配问题(困难)

1.题目描述 2.解题思路&#xff0c;这道题自己没做出来&#xff0c;看了官方的题解&#xff0c;感觉对自己来说确实是比较难想的。使用了动态规划的解决方案&#xff0c;这种方案看题解都不一定能看明白&#xff0c;不过有个评论画图讲解的非常明白。其实仔细看题解的话&#…

从一个小故事讲解观察者模式~

定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 什么是观察者模式&#xff1f; 观察者模式在我们的日常生活中极其常见。 先来看看观察者模式的定义&#xff1a; 观察者模式定义了对象之间…

面试八股文(3)

文章目录 1.HashSet如何检查重复2.comparable和Comparator区别3.ConcurrentHashMap和Hashtable区别4.线程和进程5.并发与并行的区别6.为什么使用多线程7.使用多线程可能带来问题8.线程的生命周期和状态9.什么是上下文切换10.线程死锁11.产生死锁四个条件12.如何避免死锁 1.Hash…

使用API有效率地管理Dynadot域名,使用API进将其他平台的域名转移至dynadot

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

Convolutional Neural Network (CNN) 识别手写数字字体

介绍&#xff1a; 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种在计算机视觉领域广泛应用的深度学习模型。它主要用于图像识别、目标检测、图像分割等任务。 CNN的核心思想是利用卷积操作提取图像的特征。卷积操作是一种基于滤波器…

vue3:23—自定义hooks

正是因为有了hooks&#xff0c;组合式才发挥出了威力 其实 hooks 和 vue2 中的 mixin 有点类似&#xff0c;但是相对 mixins 而言&#xff0c; hooks 更清楚复用功能代码的来源, 更清晰易懂。 如何定义hooks 具备可复用功能&#xff0c;才需要抽离为 hooks 独立文件函数名/文…

jsp粉丝社区系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 粉丝社区系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

SpringBoot注解--06--注解@Validated

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1 简述1.1 Validated作用1.2 所有参数注解含义1.3 异常处理1.4 Valid和Validated比较Valid级联校验 2.Validated 分组校验1.1为何要分组校验&#xff1f;1.2 代码案…

洞察数字未来:2023年腾讯全球数字生态大会全景解析

随着数字技术的飞速发展&#xff0c;全球正步入一个全新的数字生态时代。作为业界瞩目的科技盛会&#xff0c;2023年腾讯全球数字生态大会汇聚了众多行业领袖、技术专家和创新者&#xff0c;共同探讨数字生态的最新趋势、挑战和机遇。 本文将从大会的核心议题、分享内容及其价值…

【SpringBoot】模板设计模式应用

一、前言 常见的设计模式有23种&#xff0c;我们不得不提到模板方法设计模式&#xff0c;这是一种在软件开发中广泛使用的行为型设计模式之一。 二、模板方式是什么 全称是模板方法设计模式。 模板模式是一种行为设计模式。它的实现思路是&#xff0c;创建一个 模板方法 me…

【C语言】static关键字的使用

目录 一、静态本地变量 1.1 静态本地变量的定义 1.2 静态本地变量和非静态本地变量的区别 二、静态函数 2.1 静态函数的定义 2.2 静态函数与非静态函数的区别 三、静态全局变量 3.1 静态全局变量的定义 3.2 静态全局变量和非静态全局变量的区别 四、静态结构体变量 …

机器学习6-逻辑回归

逻辑回归是机器学习中一种常用于二分类问题的监督学习算法。虽然名字中包含“回归”,但实际上它用于分类任务,特别是对于输出为两个类别的情况。逻辑回归通过使用 logistic 函数将输入映射到一个在0,1范围内的概率值,然后根据这个概率值进行分类。 以下是逻辑回归的基本概念…

Kafka系列(一)【消息队列、Kafka的基本概念、Kafka的工作机制、Kafka可满足的需求、Kafka的特性、Kafka的应用场景】

kafka系列 一 一、消息队列1. 消息队列的来源2. 什么是消息队列3. 消息队列主要有哪些作用 二、Kafka的基本概念代理、生产者、消费者、消费者组主题、分区、副本、记录 三、了解 Kafka的工作机制-生产消息/消费消息四、Kafka可满足的需求五、Kafka的特性六、Kafka的场景 转自《…