【多线程】定时器

news2024/11/24 7:52:59

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 定时器是什么?
  • 2. 定时器的应用场景
  • 3. Timer类的使用
    • 3.1 Timer类创建定时器
    • 3.2 schedule()方法的介绍
    • 3.3 使用Timer管理多个任务
  • 4. 如何自己实现一个定时器?
    • 4.1 需考虑的问题
      • 4.1.1 如何实现自定义 MyTimer 类同时管理多个任务
      • 4.1.2 如何保证多线程下操作 PriorityQueue 线程安全
    • 4.2 实现的思路
      • 4.2.1 自定义类 MyTimer
      • 4.2.2 构造线程执行任务
    • 4.3 存在的问题
      • 4.3.1 当前队列里的 Mytask 元素是按照什么规则表示优先级的?
      • 4.3.2 while(true)带来CPU忙等问题
    • 4.4 最终完整代码

1. 定时器是什么?

定时器】就是闹钟,就是设定一个时间,当该时间一到,可执行一个指定的代码,即在预设的时间执行一个或多个动作

Java 标准库(java.util)中提供定时器类:Timer类,其核心方法:schedule()

2. 定时器的应用场景

定时器的应用场景非常多!

尤其在网络编程中,在实际中,很多的"等待"不应该是无止境地等待,应该有一个期限!比如打开浏览器访问某个网站,如果正好此时的网络信号不佳,则可能加载很长时间,浏览器会设置一个超时时间,如果访问该页面等待时间超过这个超时时间仍然没有结果,则提醒用户无需等待下去(浏览器会提示 504 gateway time out)

比如数据备份,服务器每天凌晨自动备份数据,使用定时器每天在指定时间执行数据备份的操作;再比如定时邮件发送,自动发送定时的提醒邮件等,设置定时器在特定时间执行发送邮件的操作,还有缓存更新,缓存数据定期更新,以保持数据的时效性,定时器周期性地更新缓存等等

总之,定时器在 Java 应用中十分常见,尤其是在需要按计划执行操作的时候

3. Timer类的使用

3.1 Timer类创建定时器

Timer 类提供以下 4 个构造方法:

public Timer() 
public Timer(boolean isDaemon)
public Timer(String name)
public Timer(String name, boolean isDaemon)

其中参数 name 是线程的名字,isDaemon参数设置为 true 表示将该线程设置为后台线程,默认是前台线程

(关于前后台线程可以回顾前期内容 Thread 类及其基本用法,其中属性方法是否为后台线程 --> isDaemon()中有介绍)

以下是使用 Timer 类创建定时器,具体代码如下:

public class ThreadDemo {
    public static void main(String[] args) {
        //1.创建 timer 对象
        Timer timer = new Timer();
        //2. 设定时间
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1!");
            }
        },2000);
        System.out.println("hello2!");
    }
}

打印结果:

在这里插入图片描述
运行结果可以看到,实现了该效果,先立即执行打印 hello2,等待 2s 后,执行定时器中的操作,打印 hello1

注意】这里我们可以看到打印 hello1 后,程序并未终止退出,这是为什么呢?
这里是因为 Timer 类里内置的前台线程前台线程会阻止当前进程结束!实际上,run()方法的执行正是依赖 Timer 类中内部线程控制时间到了之后再执行!!!

3.2 schedule()方法的介绍

在 Java 的 Timer 类中,schedule() 方法用于安排一个 TimerTask 任务在未来的某个时间点执行,Timer 类有几个不同的 schedule() 方法,它们之间的主要区别在于任务的执行是一次性的还是周期性的,带有参数 period 的方法则是可以周期性执行!

void schedule(TimerTask task, long delay)
void schedule(TimerTask task, Date time)
void schedule(TimerTask task, Date firstTime,long period)
void schedule(TimerTask task, long delay,long period)

以上 4 个构成重载,第一个参数均为被安排的任务,后面的参数表示设定任务将要等待的时间,具体解释说明这两个参数:task 和 delay
task】task to be scheduled,被安排的任务(安排一个工作,说明这个工作不是立刻完成,而是在未来的某个时间点完成)
delay】delay in milliseconds before task is to be executed,任务执行前的延迟时间,以毫秒为单位

public class ThreadDemo {
    public static void main(String[] args) {
        //1.创建 timer 对象
        Timer timer = new Timer();
        //2. 设定时间
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1!");
            }
        },4000);
    }
}

上述代码 schedule() 方法的第一个参数是 TimerTask,本质上就是 Runnable,通过查看 TimerTask 源代码可以看出, TimerTask 就是一个实现了 Runnable 接口的抽象类!因此,当我们创建 TimerTask 对象的时候,需要重写 run() 方法,run() 方法中写的内容定义的行为,即表示任务具体要做什么的内容

图解说明:
在这里插入图片描述

3.3 使用Timer管理多个任务

定时器内部管理的不仅仅是一个任务,可以管理很多很多任务!

那么我们会想这样一个问题,如果有很多个任务,比如几万个,假如定时器里面有几万个任务,如果创建几万个线程,会消耗非常多的资源,这是一个多么庞大的数字!!!

虽然任务可能会有很多个,它们的触发的时间是不同的,因此,只需要一个或者一组工作线程,每次找到这些任务中最先到达时间的任务,即在一个线程中,先执行最早的任务,根据触发时间依次执行,时间到了则执行,没有到则等待,这样就不需要创建那么多线程也可以按照指定时间依次执行任务了!

以下代码给定时器同时定义 5 个任务:

public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello4");
            }
        },4000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello5");
            }
        },5000);
    }
}

效果如下:
在这里插入图片描述
运行程序后,根据时间,先打印 hello1,接着每间隔 1s 打印,实现该效果!

4. 如何自己实现一个定时器?

4.1 需考虑的问题

4.1.1 如何实现自定义 MyTimer 类同时管理多个任务

从上述使用 Timer 管理多个任务可以看到,该机制是需要一个/一组工作线程,每次找到这些任务中最先到达时间的任务,在一个线程中,先执行最早的任务,根据触发时间依次执行,时间到了则执行,没有到则等待

我们自己实现 MyTimer 类,如何才能实现这样的机制呢?

!!! 堆是这里需要用到的核心数据结构,在 Java 标准库中直接就提供了优先级队列 —— PriorityQueue
因此,我们使用 PriorityQueue 来实现

可能会有疑问,为什么不用排序?
要知道,排序的效率要低于堆,并且在插入新元素的时候,想要维护原有序列的规律是比较困难的
(有直接的优先级队列,为什么不用捏!)

4.1.2 如何保证多线程下操作 PriorityQueue 线程安全

定时器可能有多个线程执行 schedule() 方法,那么希望在多线程下操作 PriorityQueue 还能够保证线程安全!

要保证线程安全,进一步我们思考,Java 标准库中提供带优先级的阻塞队列 —— PriorityBlockingQueue ,能够解决这个线程安全问题

上期内容多线程系列中介绍了阻塞队列,BlockingQueue 接口有 7 个实现类,其中有一个类是 PriorityBlockingQueue

PriorityBlockingQueue】是支持优先级排序的无界阻塞队列,它遵循优先级队列规则,即可以实现根据任务的执行时间来建立小根堆,取头元素则是当前该队列中的最小元素,并且带优先级的阻塞队列同样只有入队列和出队列有阻塞特性!其它方法则不具备阻塞特性

4.2 实现的思路

4.2.1 自定义类 MyTimer

首先创建一个自定义类 MyTimer 类来模拟 Timer 类,创建这个类要表示两方面的信息:

1)执行的任务是什么 (Mytask用于描述一个要执行的任务)
2)任务执行的时间(为后续判定方便,这里使用绝对的时间戳)

绝对时间戳
MyTimer 类中定义的schedule()方法中,其中需要传入一个参数 delay,表示的是相对时间,如 5000 ms,表示在 5s 后执行该任务,因此,在构造 MyTask 的时候,需要将相对时间转换为绝对时间,如下:

this.time = System.currentTimeMillis()+delay;

其中 System.currentTimeMillis() 用于获取当前毫秒级别的时间戳,即为当前时刻和基准时刻 ms 数之差
(基准时刻是 1970 年 1 月 1 日 00:00:00)
在这里插入图片描述
MyTimer 类封装核心数据结构 —— PriorityBlockingQueue,MyTask 作为元素放在这个优先级阻塞队列中

代码如下:

//表示一个任务
class MyTask {
    public Runnable runnable;
    //为了方便后续判断使用绝对的时间戳
    public long time;


    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻的时间戳+delay作为该任务实际执行的时间戳
        this.time = System.currentTimeMillis()+delay;
    }
}

class MyTimer {
    //这个结构就是带有优先级的阻塞队列 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //此处delay是一个形如5000这样的数字(多长时间之后执行该任务)
    //这列的元素需要手动封装
    //创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行
    public void schedule(Runnable runnable, long delay) {
        //根据参数构造MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
    }
}

4.2.2 构造线程执行任务

定时器类中的构造方法中构造一个线程负责执行具体任务,判断带有优先级的阻塞队列中的各个任务是否到达可执行的时间

先从 queue 中取出一个元素任务,这个任务的时间是所有任务执行实现最早的,再获取当前的时间,通过比较当前时间和该任务的时间,判断是否达到了该任务的执行时间,如果达到了,则执行 run()方法,未达到,则将取出的任务放回队列中

为什么要使用绝对时间?
因为我们要将执行时间与此刻的时间戳进行对比,判断是否执行任务

代码如下:

class MyTimer {
   //这个结构就是带有优先级的阻塞队列 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //此处delay是一个形如5000这样的数字(多长时间之后执行该任务)
    //这列的元素需要手动封装
    //创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行
    public void schedule(Runnable runnable, long delay) {
        //根据参数构造MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
    }

    //在这里构造一个线程负责执行具体任务
    public MyTimer() {
        Thread t = new Thread(() -> {
           while(true) {
               try {
                   synchronized (locker) {
                       //阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素
                       MyTask myTask = queue.take();
                       //当前队列无元素 队列阻塞 退出循环
                       //队列有元素就可以获取到元素
                       //看钱当前任务时间是否合适
                       long curTime = System.currentTimeMillis();
                       if (myTask.time <= curTime) {
                           //时间到了可以执行任务了
                           myTask.runnable.run();
                       } else {
                           //时间没到
                           //把刚才取出的任务重新塞回队列中
                           queue.put(myTask);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
    }
}

4.3 存在的问题

但是上述代码存在两个严重的bug!!!

4.3.1 当前队列里的 Mytask 元素是按照什么规则表示优先级的?

如果 Mytask 元素没有实现比较方法规则,在添加元素是可能会抛出 classCastException 异常,因为优先级队列无法确定对象之间排序的顺序!(注意这里我们比较的是对象,而不是简单的数字等)

忘记对象比较的小伙伴们可以看看这期内容:对象的比较

因此 PriorityBlockingQueue 可以通过直接实现 Comparable 接口并重写该接口的 compareTo 方法,也可以通过 自定义比较器类,实现Comparator接口

这里使用 Mytask 类实现Comparable 接口,并重写 compareTo 方法,通过对象的时间 time 来进行比较

代码如下:

class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为了方便后续判断使用绝对的时间戳
    public long time;
    
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻的时间戳+delay作为该任务实际执行的时间戳
        this.time = System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(MyTask o) {
    //取时间最小的元素
        return (int)(this.time-o.time);
    }
}

4.3.2 while(true)带来CPU忙等问题

忙等,CPU确实是在等着,但是没有休息,等待过程中占用着CPU,就比如我点的餐,显示8:30做好,我期待着它能提前做好,8:01去看一下,8:0去看一下餐好没好,又过5分钟,我又去看一下,剩下的时间我一直不停地看时间,去看餐到底好没好,这段时间算是浪费了!

忙等在上述代码中表现为while(true)的执行内容中,每秒钟可能访问队首元素非常多次,但是还没有到时间,离执行时间还有很久,这是在做无意义的事情,造成了CPU的浪费!

解决的方式就是需要在等待过程中释放 CPU,提到忙等,我就立马想到了使用 wait() 方法,不知道各位想起小丁忙等的故事了嘛~ wait 方便随时提前唤醒,比如当前时刻是14:30,约定14:40需要执行上课这个任务,取出队首元素,发现时间没到,则 wait 等待10min,在等待过程中,突然来一个任务,比如14:35要去接水,则就不能一直等待了,而是唤醒notify,此时工作线程就会重新取队首元素,即14:35的接水任务!

因此,时间还没有到,则将刚取出来的队首元素放回队列,并进入 wait 等待,一直到时间到,将它唤醒,如果插入新元素,调用 notify,唤醒锁对象

代码如下:

//表示一个任务
class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为了方便后续判断使用绝对的时间戳
    public long time;


    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻的时间戳+delay作为该任务实际执行的时间戳
        this.time = System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
        //取时间最小的元素
    }
}
class MyTimer {
    //创建一个锁对象
    private Object locker = new Object();

    //这个结构就是带有优先级的阻塞队列 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //此处delay是一个形如3000这样的数字(多长时间之后执行该任务)
    //这列的元素需要手动封装
    //创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行
    public void schedule(Runnable runnable, long delay) {
        //根据参数构造MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
        synchronized (locker) {
            locker.notify();
        }
    }

    //在这里构造一个线程负责执行具体任务
    public MyTimer() {
        Thread t = new Thread(() -> {
           while(true) {
               try {
                   synchronized (locker) {
                       //阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素
                       MyTask myTask = queue.take();
                       //当前队列无元素 队列阻塞 退出循环
                       //队列有元素就可以获取到元素
                       //看钱当前任务时间是否合适
                       long curTime = System.currentTimeMillis();
                       if (myTask.time <= curTime) {
                           //时间到了可以执行任务了
                           myTask.runnable.run();
                       } else {
                           //时间没到
                           //把刚才取出的任务重新塞回队列中
                           queue.put(myTask);
                           //实时时间进行调整
                           locker.wait(myTask.time-curTime);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
    }
}

深入解析:
1)为什么是在 schedule() 方法里 notify()
因为在 schedule() 方法中使用 notify 是为了确保正在等待的线程能够及时得到通知,重新检查队列并执行更早的任务,因为可能新来的任务,执行时间更早!

2)确定加锁位置问题

我们来分析一下,以下两种加锁位置:

在这里插入图片描述
第一种加锁位置即为上述代码写的,是正确的,第二种加锁位置是错误的,将会引起线程安全问题,会出现空打一炮的情况,使新的任务执行时间更早而无法及时执行

对此,我们进一步分析第二种加锁位置:假设线程 t1 执行到 put 时候,切到线程 t2 执行
在这里插入图片描述
接下来,等 t1 线程继续执行的时候,将要 wait 等待30min,线程 t2 的 notify 已经执行过了,wait 已经错过 notify了,此时的 wait 就会导致新的任务无法及时执行!!!所以是有问题的~

那第一种加锁位置,为什么就正确呢?仍然假设线程 t1 执行到 put 时候,切到线程 t2 执行
在这里插入图片描述
因此,新加一个12:10的任务,在 t1 线程执行到 wait 前,t2 线程因为没有锁进行阻塞等待,当 t1 进入 wait,t1 释放锁,t2 插入新的任务竞争到锁,并执行 notify 唤醒 t1 线程,让 t1 线程重新扫描阻塞队列中的任务,发现比原来更早的执行任务12:10,进行更新执行这个更早的任务!

总之,多线程是很复杂的,稍不留神就很容易出现错误!牢记线程安全问题的根本原因:抢占式执行!!!

4.4 最终完整代码

//表示一个任务
class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为了方便后续判断使用绝对的时间戳
    public long time;


    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻的时间戳+delay作为该任务实际执行的时间戳
        this.time = System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
        //取时间最小的元素
    }
}
class MyTimer {
    //创建一个锁对象
    private Object locker = new Object();

    //这个结构就是带有优先级的阻塞队列 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //此处delay是一个形如3000这样的数字(多长时间之后执行该任务)
    //这列的元素需要手动封装
    //创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行
    public void schedule(Runnable runnable, long delay) {
        //根据参数构造MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
        synchronized (locker) {
            locker.notify();
        }
    }

    //在这里构造一个线程负责执行具体任务
    public MyTimer() {
        Thread t = new Thread(() -> {
           while(true) {
               try {
                   synchronized (locker) {
                       //阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素
                       MyTask myTask = queue.take();
                       //当前队列无元素 队列阻塞 退出循环
                       //队列有元素就可以获取到元素
                       //看钱当前任务时间是否合适
                       long curTime = System.currentTimeMillis();
                       if (myTask.time <= curTime) {
                           //时间到了可以执行任务了
                           myTask.runnable.run();
                       } else {
                           //时间没到
                           //把刚才取出的任务重新塞回队列中
                           queue.put(myTask);
                           //实时时间进行调整
                           locker.wait(myTask.time-curTime);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hi4");
            }
        },4000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hi3");
            }
        },3000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hi2");
            }
        },2000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hi1");
            }
        },1000);

        System.out.println("hi0");
    }
}

效果如下:
在这里插入图片描述
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述✨✨✨本期内容到此结束啦~

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

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

相关文章

Unity横板动作游戏 -项目准备

项目准备 这是一篇 Unity 2022 最新稳定版本的教程同步笔记&#xff0c;本文将会讲解一些开始学习必须的条件。 安装环境 首先是安装 UnityHub&#xff0c;然后在 UnityHub 中安装 Unity 的版本(2022)。 只需要安装 开发者工具 和文档即可&#xff0c;导出到其他平台的工具等…

学习Vue2收藏这一篇就够了(如何创建Vue实例)

什么是Vue&#xff1f; Vue是什么&#xff1a;是一个用于构建用户界面的渐进式框架 什么是构建用户界面&#xff1a;基于数据动态渲染页面 什么是渐进式&#xff1a;循序渐进的学习 什么是框架&#xff1a;一整套完整的项目解决方案 创建Vue实例 核心步骤&#xff08;4步…

《javaEE篇》--单例模式详解

目录 单例模式 饿汉模式 懒汉模式 懒汉模式(优化) 指令重排序 总结 单例模式 单例模式属于一种设计模式&#xff0c;设计模式就好比是一种固定代码套路类似于棋谱&#xff0c;是由前人总结并且记录下来我们可以直接使用的代码设计思路。 单例模式就是&#xff0c;在有…

音视频入门基础:WAV专题(3)——FFmpeg源码中,判断某文件是否为WAV音频文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.wav 可以判断出某个文件是否为WAV格式的音频文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为WAV格式的音频文件呢&#xff1f;它内部其实是通过wav_probe函数来判断的。从文章《FFmpeg源码&#xff1a;av_prob…

02、爬虫数据解析-Re解析

数据解析的目的是不拿到页面的全部内容&#xff0c;只拿到部分我们想要的内容内容。 Re解析就是正则解析&#xff0c;效率高准确性高。学习本节内容前需要学会基础的正则表达式。 一、正则匹配规则 1、常用元字符 . 匹配除换行符以外的字符 \w 匹配字母或数字或下划…

软件测试---网络基础、HTTP

一、网络基础 &#xff08;1&#xff09;Web和网络知识 网络基础TCP/IP 使用HTTP协议访问Web WWW万维网的诞生 WWW万维网的构成 &#xff08;2&#xff09;IP协议 &#xff08;3&#xff09;可靠传输的TCP和三次握手策略 &#xff08;4&#xff09;域名解析服务DNS &#xff0…

一篇文章学完Python基础

1. 字符串 str1 "Hello" str2 " World" print(str1 str2) # 输出&#xff1a;HelloWorld 1.1 字符替换 text "Hello, World!" new_text text.replace("World", "Python") print(new_text) # 输出&#xff1a;…

大数据-52 Kafka 基础概念和基本架构 核心API介绍 应用场景等

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

橙单前端项目下载编译遇到的问题与解决

今天下载orange-admin前端项目&#xff0c;不过下载下来运行也出现一些问题。 1、运行出现下面一堆错误&#xff0c;如下&#xff1a; 2、对于下面这个错误 error Expected linebreaks to be LF but found CRLF linebreak-style 这就是eslint的报错了&#xff0c;可能是原作者…

全开源收银系统源码-支付通道

1.收银系统开发语言 核心开发语言: PHP、HTML5、Dart后台接口: PHP7.3后合管理网站: HTML5vue2.0element-uicssjs线下收银台&#xff08;安卓/PC收银、安卓自助收银&#xff09;: Dart3框架&#xff1a;Flutter 3.19.6助手: uniapp商城: uniapp 2.支付通道 智慧新零售收银系统…

SQL语句(以MySQL为例)——单表、多表查询

笛卡尔积&#xff08;或交叉连接&#xff09;: 笛卡尔乘积是一个数学运算。假设我有两个集合 X 和 Y&#xff0c;那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合&#xff0c;也就是第一个对象来自于 X&#xff0c;第二个对象来自于 Y 的所有可能。组合的个数即为两个集合中…

天机学堂第二天项目 添加我的课表 项目总结

目录 根据产品原型得到数据库表结构 RabbitMq监听 构造器注入 幂等 mybatisplus 分页查询的多种写法 在new page里面添加排序 查询条件中 用orderBydESC指定排序 ​编辑 链式编程中使用page指定排序 stream流 ​编辑 在网关中解析token 根据产品原型得到数据库表结构 根…

IDEA Maven使用HTTP代理,解决Could not transfer artifact org.xxx问题

文章目录 一、前言二、遇到问题三、分析问题四、HTTP代理五、重新编译验证 一、前言 遇到这个问题&#xff0c;有两种解决办法 IDEA Maven使用HTTP代理&#xff0c;解决Could not transfer artifact org.xxx问题IDEA Maven使用国内镜像&#xff0c;解决Could not transfer arti…

Matlab编程资源库(10)离散傅立叶变换

一、离散傅立叶变换算法简要 给定一个N点的离散信号序列x(n)&#xff0c;其中n表示时刻&#xff0c;n 0, 1, 2, ..., N-1。 定义离散傅立叶变换的频域序列X(k)&#xff0c;其中k表示频率&#xff0c;k 0, 1, 2, ..., N-1。 通过以下公式计算每个频率对应的复数值&#xff…

win11 备份mysql数据 卸载mysql 5 安装mysql 8详细教程(mysql的数据备份与卸载与安装)

如果是第一次安装mysql便可以直接看第三步 第一步&#xff1a;数据备份 我选择的是备份全部数据 以管理员身份运行cmd输入mysqldump -u root -p --all-databases --routines --triggers --single-transaction > C:\ProgramTools\MySql\backup_5.6.sql 其中&#xff1a;-u…

golang 文件

golang 文件 概念 文件是计算机系统中用于存储和管理的 数据集合&#xff0c;具有唯一的名称&#xff0c;存在于存储介质上&#xff0c;包含创建、修改等属性&#xff0c;通过文件系统进行组织&#xff0c;用户可进行读取、写入等操作 文件流 文件输入流&#xff08;InputS…

3D打印:重塑模具制造业的创新引擎

在科技浪潮的推动下&#xff0c;3D打印技术正以前所未有的速度渗透到制造业的核心&#xff0c;尤其在模具制造领域&#xff0c;它正引领一场深刻的创新革命。该技术通过颠覆传统制造范式&#xff0c;显著优化了模具生产的复杂流程&#xff0c;实现了从设计到成品的一体化的高效…

Git操作快速入门:掌握代码版本控制的秘诀

文章目录 文章内容概述Git的安装和配置创建和克隆仓库基本操作添加和提交文件查看状态和日志分支管理远程仓库操作 常见的错误和解决方法错误1&#xff1a;合并冲突&#xff08;Merge Conflict&#xff09;错误2&#xff1a;丢失修改&#xff08;Detached HEAD&#xff09;错误…

win11查找句柄泄露

1.打开任务管理器&#xff0c;不会的网上搜 2.选择详细信息 3.注意了 比较坑的一点 win11上详细信息不会默认显示句柄数。&#xff08;默认没有句柄那一列&#xff0c;妈的花了我好长时间找&#xff09; 右键&#xff0c;点击选择列 选择句柄 下面的列表里就能看到进程使用…

CSS技巧专栏:一日一例 11 -纯CSS实现多彩渐变按钮系列特效

CSS技巧专栏:一日一例 11 -纯CSS实现多彩渐变按钮系列特效 本篇,推荐给你几个按钮,先看一下图片 本例图片 案例分析 这是一个系列的按钮,它们具有共同的特点: 底层按钮层,具有一个彩色的渐变边框,上层是依据hover效果需要,可以是渐变,可以时白色。 鼠标hover效果…