定时器 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() {
            @Override
            public 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() {
            @Override
            public void run() {
                System.out.println("3000ms Hello timer!!");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000ms Hello timer!!");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public 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;
    }

    @Override
    public 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;
    }

    //用于比较执行顺序
    @Override
    public 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最小,即最先要执行的那个任务。

    //用于比较执行顺序
    @Override
    public 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() {
            @Override
            public void run() {
                System.out.println("3000ms Hello timer!!");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000ms Hello timer!!");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1000ms Hello timer!!");
            }
        }, 1000);
    }
}

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

MyTimer timer = new MyTimer();

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

5161fd549c6d4c36af5334b9b85e830e.png

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("3000ms Hello timer!!");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000ms Hello timer!!");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public 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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Angular 基础] - Angular 渲染过程 组件的创建

[Angular 基础] - Angular 渲染过程 & 组件的创建 之前的笔记为了推进度写的太笼统了&#xff08;只有功能没有其他&#xff09;&#xff0c;当时学的时候知道是什么东西&#xff0c;但是学完后重新复习发现有些内容就记不清了&#xff0c;所以重新用自己的语言总结一下 …

Linux 多线程 | 线程的互斥

在前面的文章中我们讲述了多线程的一些基本的概念以及相关的操作&#xff0c;那么在本章中我们就将继续讲述与多线程相关的同步与互斥之间的问题。 首先我们使用一个例子引出我们的问题&#xff0c;又一个全局的变量g_val 100&#xff0c;这个变量是被所有的执行流所共享的&a…

MySQL进阶45讲【10】MySQL为什么有时候会选错索引?

1 前言 前面我们介绍过索引&#xff0c;在MySQL中一张表其实是可以支持多个索引的。但是&#xff0c;写SQL语句的时候&#xff0c;并没有主动指定使用哪个索引。也就是说&#xff0c;使用哪个索引是由MySQL来确定的。 大家有没有碰到过这种情况&#xff0c;一条本来可以执行得…

VSCode snippets 自定义Vue3代码片段(持续更新)

在编写Vue代码时发现VSCode中的各类snippets插件无法提供一些常用的代码片段,为避免重复造轮子,提高编码效率,特意自己定义了一些代码片段。为方便初学者,提供了自定义代码片断的方法。 一、 自定义代码片断的方法 1.打开命令面板(Ctrl+Shift+P) 2. 输入 user Snippets…

Hadoop3.x基础(3)- Yarn

来源&#xff1a;B站尚硅谷 目录 Yarn资源调度器Yarn基础架构Yarn工作机制作业提交全过程Yarn调度器和调度算法先进先出调度器&#xff08;FIFO&#xff09;容量调度器&#xff08;Capacity Scheduler&#xff09;公平调度器&#xff08;Fair Scheduler&#xff09; Yarn常用命…

C语言-2

自定义类型 基本认识 /*引入&#xff1a;学生&#xff1a;姓名&#xff0c;学号&#xff0c;年龄&#xff0c;成绩请为学生们专门定制一个类型&#xff08;创造一个类型&#xff09;结构体格式&#xff1a;struct 标识符 // 标识符即自定义类型的名称{成员; // 自己设置…

【Unity知识点详解】自定义程序集

今天来介绍一下Unity中的自定义程序集。在项目开发中我们经常接触到第三方插件的程序集&#xff0c;如DOTween、Newtonsoft.Json等。 使用自定义程序集有这么几个好处&#xff1a; 方便代码的的复用。当某一功能模块需要在多个项目中重复使用时&#xff0c;可以将代码编译成程…

新书速览|Kubernetes从入门到DevOps企业应用实战

从0到1&#xff0c;从零开始全面精通Kubernetes&#xff0c;助力企业DevOps应用实践 本书内容 《Kubernetes从入门到DevOps企业应用实战》以实战为主&#xff0c;内容涵盖容器技术、Kubernetes核心资源以及基于Kubernetes的企业级实践。从容器基础知识开始&#xff0c;由浅入深…

C#中使用OpenCvSharp4绘制直线、矩形、圆、文本

C#中使用OpenCvSharp4绘制直线、矩形、圆、文本 继之前的Python中使用Opencv-python库绘制直线、矩形、圆、文本和VC中使用OpenCV绘制直线、矩形、圆和文字&#xff0c;将之前的Python和C示例代码翻译成C#语言&#xff0c;很简单&#xff0c;还是借用OpenCvSharp4库中的Line、…

基于腾讯云服务器搭建幻兽帕鲁服务器保姆级教程

随着网络游戏的普及&#xff0c;越来越多的玩家希望能够拥有自己的游戏服务器&#xff0c;以便能够自由地玩耍。而腾讯云服务器作为一个优秀的云计算平台&#xff0c;为玩家们提供了一个便捷、稳定、安全的游戏服务器解决方案。本文将为大家介绍如何基于腾讯云服务器搭建幻兽帕…

Fink CDC数据同步(三)Flink集成Hive

1 目的 持久化元数据 Flink利用Hive的MetaStore作为持久化的Catalog&#xff0c;我们可通过HiveCatalog将不同会话中的 Flink元数据存储到Hive Metastore 中。 利用 Flink 来读写 Hive 的表 Flink打通了与Hive的集成&#xff0c;如同使用SparkSQL或者Impala操作Hive中的数据…

从MySQL到TiDB:兼容性全解析

MySQL 在高并发和大数据量场景下&#xff0c;单个实例的扩展性有限。而 TiDB 作为一款分布式NewSQL数据库&#xff0c;设计之初就支持水平扩展&#xff08;Scale-Out&#xff09;&#xff0c;通过增加节点来线性提升处理能力和存储容量&#xff0c;能够很好地应对大规模数据和高…

AS-V1000 视频监控平台产品介绍:客户端功能介绍(一)

目 录 一、引言 1.1 AS-V1000视频监控平台介绍 1.2平台服务器配置说明 二、软件概述 2.1 客户端软件用途 2.2 客户端功能 三、客户端功能说明 3.1 登陆和主界面 3.1.1登陆界面 3.1.2登陆操作 3.1.3主界面 3.1.4资源树 3.2 视频预览 3.2.1视频预览界面 3.2.…

python 基础知识点(蓝桥杯python科目个人复习计划33)

今日复习内容&#xff1a;以做题为主 例题1&#xff1a;小蓝的漆房 题目描述&#xff1a; 小蓝是一位有名的漆匠&#xff0c;他的朋友小桥有一个漆房&#xff0c;里面有一条长长的走廊&#xff0c;走廊两旁有许多相邻的房子&#xff0c;每间房子最初被涂上了一种颜色。 小桥…

Lambda表达式(匿名函数)

C11中引入了lambda表达式&#xff0c;定义匿名的内联函数。 我们可以直接原地定义函数而不用再跑到外面去定义函数跳来跳去。 同时在stl的排序上也有作用。 [capture] (parameters) mutable ->return-type {statement}下面逐一介绍各个参数的含义. [capture] : 捕获&#…

喜讯!亚信安慧斩获第六届金猿奖两大奖项!

近日&#xff0c;第六届金猿奖颁奖典礼在上海 “第六届金猿季&魔方论坛——大数据产业发展论坛”上隆重举行&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称“亚信安慧”&#xff09;凭借AntDB数据库获评“2023大数据产业年度创新技术突破” 、“2023大数据产业年度…

解锁MyBatis Plus的强大功能:学习高级操作与DML技巧!

MyBatisPlus 1&#xff0c;DML编程控制1.1 id生成策略控制知识点1&#xff1a;TableId1.1.1 环境构建1.1.2 代码演示AUTO策略步骤1:设置生成策略为AUTO步骤3:运行新增方法 INPUT策略步骤1:设置生成策略为INPUT步骤2:添加数据手动设置ID步骤3:运行新增方法 ASSIGN_ID策略步骤1:设…

【数据结构 09】哈希

哈希算法&#xff1a;哈希也叫散列、映射&#xff0c;将任意长度的输入通过散列运算转化为固定长度的输出&#xff0c;该输出就是哈希值&#xff08;散列值&#xff09;。 哈希映射是一种压缩映射&#xff0c;通常情况下&#xff0c;散列值的空间远小于输入值的空间。 哈希运…

thinkadmin的form.html表单例子

<style>textarea {width: 100%;height: 200px;padding: 10px;border: 1px solid #ccc

JUC并发工具类的应用场景详解

目录 常用并发同步工具类的真实应用场景 1. ReentrantLock 1.1 常用API 1.2 ReentrantLock使用 独占锁&#xff1a;模拟抢票场景 公平锁和非公平锁 可重入锁 结合Condition实现生产者消费者模式 1.3 应用场景总结 2. Semaphore 2.1 常用API 2.2 Semaphore使…