2.多线程-初阶(上)

news2024/11/25 17:12:51

文章目录

  • 1. 认识线程(Thread)
    • 1.1 概念
    • 1.2 第一个多线程程序
    • 1.3 创建线程
      • 1.3.1方法1 继承 Thread 类
      • 1.3.2方法2 实现 `Runnable` 接口
    • 1.4 多线程的优势-增加运行速度
    • 1.5 PCB、PID、进程和线程之间的关系
  • 2. Thread(/θred/) 类及常见方法
    • 2.1 Thread 的常见构造方法
    • 2.2 Thread 的几个常见属性
    • 2.3 启动一个线程-start()
    • 2.4 中断一个线程
    • 2.5 等待一个线程-join()
    • 2.6 获取当前线程引用
    • 2.7 休眠当前线程

大家好,我是晓星航。今天为大家带来的是 多线程-初阶 相关的讲解!😀

1. 认识线程(Thread)

1.1 概念

1) 线程是什么

一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

还是回到我们之前的银行的例子中。之前我们主要描述的是个人业务,即一个人完全处理自己的业务。我们进一步设想如下场景:

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。

如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。

此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。

2) 为啥要有线程

首先, “并发编程” 成为 “刚需”.

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.
  • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.(线程之所以轻,是因为把申请资源/释放资源的操作给省下了)

  • 创建线程比创建进程更快.
  • 销毁线程比销毁进程更快.
  • 调度线程比调度进程更快.

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)

关于线程池我们后面再介绍. 关于协程的话题我们此处暂时不做过多讨论.

3) 进程和线程的区别

  • 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.

比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

相当于我们只增加处理资源的线程,把申请资源和释放资源的操作省下来了!!!

如果我们进行多线程操作,相当于只有第一个线程启动的资源开销是比较大的,后续线程的加入就很简单了。同一个进程里的多个线程之间,共用了进程的同一份资源(主要指的是 内存 和 文件描述符表)。

注:一个线程只能在一个进程中,但是一个进程可以包含多个线程。

4) Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

如果一个线程抛异常,处理不好,很可能把其他线程都给带走了,导致所有线程都挂了。

1.2 第一个多线程程序

感受多线程程序和普通程序的区别:

  • 每个线程都是一个独立的执行流
  • 多个线程之间是 “并发” 执行的.

并行:微观上同一时刻,两个核心上的进程,就是同时执行的

并发:微观上,同一时刻,一个核心上只能运行一个进程。但是它能够对进程快速的进行切换,比如说 CPU 这个核心上,先运行一下 QQ音乐,再运行以下 cctalk ,再以下LOL,只要切换速度足够快(2.5GHz,每秒运行 25亿条指令),宏观上认识感知不到的

未来除非显式声明,否则谈到并发,就是指并行 +并发。

import java.util.Random;    
public class ThreadDemo {
        private static class MyThread extends Thread {
            @Override
            public void run() {
                Random random = new Random();
                while (true) {
                    // 打印线程名称
                    System.out.println(Thread.currentThread().getName());
                    try {
                        // 随机停止运行 0-9 秒
                        Thread.sleep(random.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        public static void main(String[] args) {
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
            MyThread t3 = new MyThread();
            t1.start();
            t2.start();
            t3.start();
            Random random = new Random();
            while (true) {
                // 打印线程名称
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                    // 随机停止运行 0-9 秒
                    e.printStackTrace();
                }
            }
        }
    }
Thread-0
Thread-0
Thread-2
Thread-1
Thread-2
Thread-1
Thread-0
Thread-2
main
main
Thread-2
Thread-1
Thread-0
Thread-1
main
Thread-2
Thread-2
......

使用jconsole命令观察线程

1.3 创建线程

1.3.1方法1 继承 Thread 类

  1. 继承 Thread 来创建一个线程类.
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
   }
}
  1. 创建 MyThread 类的实例
MyThread t = new MyThread();
  1. 调用 start 方法启动线程
t.start(); // 线程开始运行

上述操作中有解耦合。

解耦合:目的就是为了让 线程 和 线程 要干的活之间分离开。未来如果要改代码,不用多线程,使用多进程,或者线程池,或者协程…此时代码改动比较小

1.3.2方法2 实现 Runnable 接口

  1. 实现 Runnable 接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
   }
}
  1. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
  1. 调用 start 方法
t.start(); // 线程开始运行

对比上面两种方法:

  • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
  • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

其他变形

  • 匿名内部类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
   }
};
  • 匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
   }
});
  • lambda 表达式创建 Runnable 子类对象
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
    System.out.println("使用匿名类创建 Thread 子类对象");
});

1.4 多线程的优势-增加运行速度

可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

  • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {
    // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
    private static final long count = 10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
        // 使用并发方式
        concurrency();
        // 使用串行方式
        serial();
   }
    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();
        
        // 利用一个线程计算 a 的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
               }
           }
       });
        thread.start();
        // 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
       }
        // 等待 thread 线程运行结束
        thread.join();
        
        // 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发: %f 毫秒%n", ms);
   }
    private static void serial() {
        // 全部在主线程内计算 a、b 的值
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
       }
        int b = 0;
        for (long i = 0; i < count; i++) {
          b--;
       }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串行: %f 毫秒%n", ms);
   }
}
并发: 399.651856 毫秒
串行: 720.616911 毫秒

1.5 PCB、PID、进程和线程之间的关系

PCB 对应的是线程。

一个线程对应一个PCB。

一个进程对应多个PCB。

如果一个进程只有一个线程,就是一个进程对一个PCB了。

同一个进程里的若干PCB、PID相同,不同进程的 PID 是不同的。

2. Thread(/θred/) 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关 联。

用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象 就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

注:我们使用Thread类时不必要import一个包,因为我们的Thread就再java.lang下面。

2.1 Thread 的常见构造方法

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

注:这里的3和4方法多出来的String name的作用是给我们的线程起名字。

2.2 Thread 的几个常见属性

  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,下面我们会进一步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

前台线程:会阻止进程结束,前台线程的工作没做完,进程是不可结束的。

后台线程:不会阻止进程结束,后台线程工作没做完,进程也是可以结束的。

代码里手动创建的线程,默认都是前台的。包括 main 默认也是前台的。其他 jvm 自带的线程都是后台的,也可以手动的使用 setDaemon 设置成后台线程。是后台线程就是守护线程。

即isDaemon()返回为true 那么该线程就是后台线程。

  • 是否存活,即简单的理解,为 run 方法是否运行结束了

isAlive() 是在判断,当前系统里面的这个 线程 是不是真的有了。

另外,如果内核里线程把 run 干完了,此时线程销毁,pcb随之释放。但是 Thread t 这个对象还不一定被释放的。此时isAlive() 也是 false。(这个函数只关注内核里的线程是否在工作,不关注Thread所创建的对象是否还存在)

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                    for (int i = 0;i < 3;i++) {
                        System.out.println("hello");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
            }
        },"mythread");
        t.start();
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(t.isAlive());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在执行完for的三次后,t被销毁,因此后续的isAlive()返回的都是false。

  • 线程的中断问题,下面我们进一步说明
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还
活着");
                    Thread.sleep(1 * 1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
       });
        System.out.println(Thread.currentThread().getName() 
                           + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName() 
                           + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName() 
                           + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName() 
                           + ": 优先级: " + thread.getPriority());
                           System.out.println(Thread.currentThread().getName() 
                           + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() 
                           + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName() 
                           + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName() 
                           + ": 状态: " + thread.getState());
   }
}

2.3 启动一个线程-start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程 就开始运行了。

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。

调用 start 方法, 才真的在操作系统的底层创建出一个线程.

通过循环打印"hello world" 和 "hello thread"来观察两个线程是怎么工作的。

由上图可知"hello world" 和 "hello thread"这两个字符串循环打印。

注:这里的"hello world" 和 "hello thread"他们的打印顺序是随机的,内核里本身并非是随机的,但是干扰因素太多,并且应用程序这一层也无法感知到细节,就只能认为是随机的了!

如果把上述代码的t.start改成t.run那么会在run中出不来,相当于只有一个线程在干活!!!

C:\Program Files\Java\jdk1.8.0_192\bin在这里我们可以找到jconsole这个查看进程的工具

找到我们idea中运行的这个进程

由于是我们自己的电脑,所以很安全不会存在不安全一说。

连接完选择线程这一类,我们就可以很清楚的看到我们thread中的所有线程。

被我们红方框圈出来的就是我们的调用栈,描述了当前方法之间的调用关系。

2.4 中断一个线程

我们线程中的中断不是让线程立即就停止,而是通知线程你应该要停止了。是否真的停止,取决于线程这里具体的代码写法。此时线程有三个选择:

1.立即中断

2.稍后中断

3.不中断

李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们 需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如 何通知李四停止呢?这就涉及到我们的停止线程的方式了。

目前常见的有以下两种方式:

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

示例-1: 使用自定义的变量来作为标志位.

  • 我们自定义falg为标志位,并在一开始设置为true
package thread;
public class ThreadDemo8 {
    private static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
           while (flag) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        Thread.sleep(3000);
        //在主线程里就可以随时通过 flag 变量的取值,来操作 t 线程是否结束。
        flag = false;
    }
}

因为这里休眠3000毫秒后,flag变为false,因此我们的线程循环while中变为false而终止线程。

示例-2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定 义标志位.

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

  • 使用 thread 对象的 interrupted() 方法通知线程结束.

thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志

    1. 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的一个中断标志被设置,thread 可以通过

    1. Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志 false 变 true
    2. Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置不清除中断标志 中断标志为false

这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
           while (!Thread.currentThread().isInterrupted()) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

这里interrupt在将线程内部的标志位(boolean)给设置为true,如果线程在进行sleep,就会触发异常,把sleep唤醒。

但是sleep在唤醒时,还会做一件事,把刚才设置的这个标志位,再设置回false。(清空了标志位)

这就导致了sleep的异常被catch完了之后,循环还要继续执行。

我们这里为大家提供了解决这个方法的三个情况:

1.

线程t忽略了你的终止请求。

2.

线程t立即响应你的终止请求

3.

稍后进行终止

唤醒之后线程到底要终止,还是要执行,到底是立即终止还是稍后,就把选择权交给程序猿自己了。

示例-3 观察标志位是否清除

标志位是否清除, 就类似于一个开关.

Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”

Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 “不清除标志位”.

  • 使用 Thread.isInterrupted() , 线程中断会清除标志位.
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.interrupted());
           }
       }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        thread.start();
        thread.interrupt();
   }
}

true // 只有一开始是 true,后边都是 false,因为标志位被清
false
false
false
false
false
false
false
false
false
  • 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
 System.out.println(Thread.currentThread().isInterrupted());
           }
           }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        thread.start();
        thread.interrupt();
   }
}
true // 全部是 true,因为标志位没有被清
true
true
true
true
true
true
true
true
true

2.5 等待一个线程-join()

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转 账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() 
                                       + ": 我还在工作!");
                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
       };
        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
   }
}

大家可以试试如果把两个 join 注释掉,现象会是怎么样的呢?

附录

package thread;
public class ThreadDemo9 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("join 之前");
        //此处的 join 就是让当前的 main 线程来等到 t 线程执行结束 (等待 t 的 run 执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join 之后");
    }
}

本身执行完start之后,t线程和main线程就并发执行,分头行动。

main继续往下执行,t也会继续往下执行。

遇到t.join()

就会发生阻塞

一直阻塞到,t线程结束,main线程才会从join中恢复过来,才能继续往下执行。(t线程肯定比main线程先结束)

如果开始执行join的时候已经结束了,join就不会阻塞,就会立即返回。

2.6 获取当前线程引用

这个方法我们以及非常熟悉了

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

在哪个线程中调用,就能获取到哪个线程的实例。

2.7 休眠当前线程

也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实 际休眠时间是大于等于参数设置的休眠时间的。

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3 * 1000);
        System.out.println(System.currentTimeMillis());
   }
}

被sleep的PCB(线程)就相当于放到了阻塞队列中,我们程序继续运行非阻塞队列,当sleep的时间耗完时,我们的PCB就回到非阻塞队列中继续运行。

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

《Maven实战》读后感

目录 一、一些思考1、为什么平时编写的Java项目也叫做Maven项目&#xff1f;2、平常的Java项目目录为什么长这样&#xff0c;可以改变目录结构吗&#xff1f;3、对于Maven项目来说&#xff0c;Maven能帮我们做什么&#xff1f;4、为什么一定需要Maven私服&#xff0c;不要行不行…

easyrecovery数据恢复软件2023免费版下载

easyrecovery数据恢复软件2023免费版下载是一款操作简单、功能强大数据恢复软件,通过easyrecovery可以从硬盘、光盘、U盘、数码相机、手机等各种设备中恢复被删除或丢失的文件、图片、音频、视频等数据文件。 EasyRecovery数据恢复软件是一款功能强大的数据恢复软件&#xff0c…

卡尔曼滤波的理解

看了B站up主DR_CAN讲的卡尔曼滤波&#xff08;链接&#xff09;。up讲的非常好&#xff0c;强烈推荐&#xff0c;看完终于明白了卡尔曼滤波的奥秘。下面是我对其中内容的注解&#xff0c;或者说自己的理解。大部分推导省略了&#xff0c;但保留了算法的思想脉络。 引入 首先看…

Verilog基础之十七、锁相环PLL

目录 一、前言 1.1 背景 1.2 PLL结构 二、工程设计 2.1 PLL IP核配置 2.2 设计代码 2.3 测试代码 2.4 仿真结果 2.5 常见问题 一、前言 1.1 背景 若将一个FPGA工程看做一个人体&#xff0c;时钟的重要性丝毫不亚于心脏对于人体的重要性&#xff0c;时钟的每一个周期对…

支付、购物车、搜索、文件上传、登录、还款、订单测试怎么做?

支付功能怎么测试&#xff1a;1、从功能方面考虑&#xff1a; 1&#xff09;、正常完成支付的流程&#xff1b; 2&#xff09;、支付中断后继续支付的流程&#xff1b; 3&#xff09;、支付中断后结束支付的流程&#xff1b; 4&#xff09;、单订单支付的流程&#xff1b; 5&am…

【无标题】(前沿)

Java编程语言 目前为止最流行的 是Java编程语言 但是编程与语言有很多中php。phyone。 c c. c# java html. css javascript vue() 说到计算机有很多同学会说&#xff0c;就有很多人会说35的节点&#xff0c;我问一下同学们现在哪一个行业&#xff0c;是没有35岁的节点&#x…

7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

文章目录 前言一、定义注解annotation二、拦截角色注解1. 在拦截器哪里拦截&#xff1f;2. 如何拦截角色注解&#xff1f;3. 角色如何读取?4. 最后做角色校验 三、应用&#xff1a;给管理员操作接口加注解四、PostMan测试最后 前言 在【7.1】管理员图书录入和修改API&#xf…

c语言指针进阶(二)

目录 引言 函数指针数组 指向函数指针数组的指针 回调函数 引言 大家好&#xff0c;我是c语言boom成家宝&#xff0c;今天博主带来的依然是指针的进阶讲解。上一篇博客博主有介绍指针&#xff0c;数组指针&#xff0c;指针数组&#xff0c;以及函数指针的概念以及应用&…

【Azure】Azure成本管理:规划、监控、计算和优化成本 [文末送书]

开篇先来一个不是总结的总结&#xff1a;平衡成本与性能始终是一个重大挑战。&#xff08;此处省略各种场景的解释&#xff09; 文章目录 前言一、Azure 成本管理工具1.1 什么是成本管理1.2 成本管理的主要功能 二、Azure 中可能影响成本的因素2.1 影响成本的因素2.1.1 资源类型…

leetcode|math|9.172.69.50.

9. Palindrome Number to_string 就行 172. Factorial Trailing Zeroes 不能直接乘起来&#xff0c;会overflow&#xff01;&#xff01; 166! 就是要找166乘到1一共有几个5。5&#xff0c;10&#xff0c;15&#xff0c;25...都算。166/5就是算一共有几个5。但是25其实贡献了…

【周末闲谈】感受AI时代魅力,创意无界限

i 个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 文章目录 前言人工智能的应用领域问题求解逻辑推理与定理证明自然语言处理智能信息检索技术专家系统 人工智能的三大短板展望未来从专用智能向通用智能发展从人工智能向人机混合智能发展…

Jdk 版本升级

Jdk 版本升级(多版本配置&#xff09; 一、配置多版本 首先如果系统第一次安装JDK 1.登录oracle官网Java Downloads | Oracle下载&#xff0c;此教程以Jdk1.8为例&#xff0c;一键下一步即可安装成功。 2.配置环境变量 然后在系统path路径中添加&#xff1a; %JAVA_HOME%…

单片机第一季:零基础10——串口通信和RS485

目录 1&#xff0c;串口通讯基础 1.1&#xff0c;同步和异步 1.2&#xff0c;并行和串行 1.3&#xff0c;单工、半双工与全双工通信 1.4&#xff0c;通信速率 2&#xff0c;单片机串口通讯 2.1&#xff0c;接口标准 2.2&#xff0c;通讯协议 2.3&#xff0c;串口…

834. 树中距离之和

给定一个无向、连通的树。树中有 n 个标记为 0…n-1 的节点以及 n-1 条边 。 给定整数 n 和数组 edges &#xff0c; edges[i] [ai, bi]表示树中的节点 ai 和 bi 之间有一条边。 返回长度为 n 的数组 answer &#xff0c;其中 answer[i] 是树中第 i 个节点与所有其他节点之间…

opencv环境搭建

1. 上网&#xff08;你懂的&#xff0c;没有网装不了&#xff09; 2. 参考视频&#xff1a;https://www.bilibili.com/video/BV1R44y157hW/?spm_id_from333.880.my_history.page.click&vd_source377867a48dd3d812b9d6521c8fc76de2 3. 这里我选择的是4.8的版本。 4. cmak…

charles中下载web证书

1.点击help&#xff0c;选中ssl Proxying ,点击Install Charles Root Certificate 2:点击”安装证书”按钮 3&#xff1a;点击”下一步”按钮 4&#xff1a;选中”将所有的证书都放入下列存储”&#xff0c;点击”游览”按钮 5&#xff1a;选中”受新任的根证书颁发机构”&…

day37-框架

0目录 框架 1.框架介绍 2. SSM三大框架简介 3.Mybatis 4.拓展 1.框架介绍 1.1 为什么使用框架&#xff1f; &#xff08;1&#xff09;框架效率高&#xff0c;成本低 &#xff08;2&#xff09;框架是别人写好的构建&#xff0c;我们只需学会如何使用它&#xff08;可维护性…

C++(12):动态内存

除了自动和static对象外&#xff0c;C还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建是无关的&#xff0c;只有当显式地被释放时&#xff0c;这些对象才会销毁。 静态内存 用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。 栈内存 用来…

mysql_to_clickhouse同步方案调研

调研时间&#xff1a;2021年08月&#xff0c;之后是否出现优化方案未知 方式1&#xff1a;网上开源python脚本-----mysql-clickhouse-replication 安装参考&#xff1a;https://www.cnblogs.com/gomysql/p/11199856.html 软件路径&#xff1a;https://github.com/yymysql/my…

【C++STL】模拟实现vector容器

文章目录 前言一、vector的成员函数二、增删查改工作说明size()和capapcity()2.1reserve()2.2 resize&#xff08;&#xff09;2.3 insert()2.4 erase&#xff08;&#xff09;2.5 push_back&#xff08;&#xff09;和pop_back&#xff08;&#xff09; 三、[]重载和迭代器3.1…