当前位置: 首页 > news >正文

【JavaEE】Java的多线程编程基础知识 -- 多线程篇(2)

Java多线程编程基础知识

  • 一、多线程的创建
  • 二、Thread类常用的方法和API
    • 2.1 Thread 的几个常见的属性
    • 2.2 start 启动一个线程
    • 2.3 终止一个线程
    • 2.4 等待一个线程-join()
    • 2.5 线程休眠函数 -sleep()
  • 三、线程状态
    • 3.1 观察所有线程的状态
    • 3.2 线程状态和线程转移的意义
  • 四、线程安全(重点)
    • 4.1 观察线程不安全的现象
    • 4.2 线程安全的概念
    • 4.3 线程安全出现的原因
    • 4.4 synchronized 关键字 --加锁手段
    • 4.5 volatile关键字
  • 五、线程同步
    • wait 和 notify

一、多线程的创建

构造方法

在这里插入图片描述

Java创建多线程示例

  1. 创建一个类来继承Thread, 并重写run方法

class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("hello word!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Test1 {public static void main(String[] args) throws InterruptedException {MyThread thread = new MyThread();thread.start();while (true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 创建一个类实现Runnable, 并重写run方法
class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("Hello Runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Test2 {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread t = new Thread(myRunnable);t.start();}
}
  1. 基于匿名内部类, 继承Thread, 并重写run方法
public class Test3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("Hello anonymous");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();}
}
  1. 基于内部类, 实现Runnable, 并重写run方法
public class Test4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("Hello anonymous runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();}
}
  1. 使用lambda表达式, 来表示run方法 --推荐使用
public class Test5 {public static void main(String[] args) {Thread t = new Thread(()->{while (true) {System.out.println("Hello lambda");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}
  1. 基于Callable

public class Test6 {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();Integer result = futureTask.get();System.out.println(result);}
}
  1. 基于线程池

public class Test7 {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(4);for (int i = 0; i < 100; i++) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("Hello threadPool: executorService");}});}}
}

多线程的命名

  • 多线程的默认命名是 thread + 线程编号(从0开始逐渐递增)
  • 通过构造函数来指定线程名

在这里插入图片描述

run方法 和 start方法的区别

  • 观察现象
    在这里插入图片描述
    在这里插入图片描述

  • 得出结论

    • run方法是继承Thread并重写run方法匿名类中的一个方法, 调用该方法就只是调用该方法, 并没有创建出新的线程
    • start方法是先创建出来一个新的线程, 在让该新的线程来执行run方法

二、Thread类常用的方法和API

2.1 Thread 的几个常见的属性

在这里插入图片描述

ID

这个是线程的身份标识 – 在JVM中给线程设定的标识

线程的不同身份标识:

  1. JVM有一个身份表示
  2. pthread 库(系统给程序员提供的操作线程的API), 也有一个线程的身份标识
  3. 在内核里, 针对线程的 PCB 还有身份标识

不同身份标识的意义: 可以解耦合!

状态和优先级

在这里插入图片描述

是否是后台线程 – isDaemon()

  • 前台线程: 前台线程会影响进程, 如果前台线程没有结束, 则进程就不会结束, 如果所有的前台线程都结束了, 即使还存在后台线程, 进程也会推出
  • 后台线程/守护线程: 后台线程不会影响进程的结束与否

是否存活 – isAlive()

Thread 对象, 对应的线程(系统内核中), 是否存活;

Thread 对象的生命周期, 并不是和系统中的线程完全一致的!

2.2 start 启动一个线程

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

  • 覆盖 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法, 就是喊一声:"行动起来! " , 线程才真正独立去执行了
    在这里插入图片描述

2.3 终止一个线程

  • 线程结束的标志
    • run 函数结束
  • 此处的终止线程, 就是想办法, 让 run 能够尽快的执行完毕

终止线程的方法

  1. 程序员手动设定标志位
public class Test02 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!isQuit) {System.out.println("Hello world");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();Thread.sleep(3000);isQuit = true;System.out.println("把t线程终止了");}
}

在这里插入图片描述

lambda表达式捕捉变量的理解
  • 问题提出:

    • lambda表达是可以捕获到外面的变量, 既然是lambada 表达式, 执行时机更靠后的, 这就导致了, 后续真正执行 lambda 的时候, 局部变量 isQuit 是否可能已经被销毁了呢?
    • 这种情况是客观存在的, 让 lambda 去访问一个已经被销毁了变量很明显是不合适的。
  • 问题解决

    • lambda 引入了“变量捕获”这样的机制,lambda 内部看起来是直接访问外部的变量,其实本质上是把外部的变量复制了一份,到 lambda 里面。(这样就可以解决刚才 生命周期 的问题了)
    • 所以,变量捕获这里的限制,要求捕获的变量得是 final (至少 事实上是 final)-- 就是该变量只有一次赋值操作
  • 如果这个变量想要修改,就不能进行变量捕获了

    • 为什么java这么设定?
      • 因为 java 是通过复制的方式来实现 “变量捕获”。如果外面的代码想要这个变量进行修改,就会出现一个情况,外面的变量变了,里面的没变 – 代码更容易出现歧义
  • 相比较之下,其它语言(JS)采取了更激进的设计 – 也有变量捕获,不是通过复制的方式实现,而是直接改变外部变量的生命周期从而保证 lambda 在执行的时候肯定能访问到外部的变量 – 此时JS的变量捕获就没有final的限制


  1. 直接使用 Thread 类,给我们提供好了现成的标志位,不同咱们手动设置这个标志。
public class Test03 {public static void main(String[] args) throws InterruptedException {Thread t =new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("Hello world");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t.start();Thread.sleep(3000);t.interrupt(); // 将 Thread 中断标志位设置为 trueSystem.out.println("把t线程终止了");}
}

运行现象

在这里插入图片描述

  • t 线程正在sleep, 然后被 interrupt 给唤醒了, 而手动设置的标志位, 此时是没法唤醒sleep的
  • 因此, 线程正在 sleep 过程中, 其它线程调用 interrupt 方法, 就会强制 sleep 抛出一个异常, sleep 就会被立即唤醒了, 但 sleep 在唤醒的同时, 会自动清除前面设置的标志位! – 这样就给程序员留下更多的操作空间
    • 我们通过 try - catch 捕获 InterruptedException 异常, 在catch 中写自己想要的处理逻辑
      • a. 立即停止循环, 立即结束线程
      • b. 继续做点别的事情, 过一会再结束线程
      • c. 忽略终止的请求, 继续循环

2.4 等待一个线程-join()

  • 问题分析
    • 多个线程是并发执行的, 具体的执行过程, 都是由操作系统负责调度的, 操作系统的调度线程的过程, 是 “随机” 的, 无法确定, 线程执行的先后顺序, 上述随机性, 程序员不太喜欢, 更喜欢的是"确定"的东西
  • 等待线程 是规划 线程结束顺序的一种手段

join() 方法的参数

在这里插入图片描述

  • 没有参数的是阻塞的等待线程结束, 就是让等待线程不在执行代码了, 不在使用CPU的资源了 – 但是死的策略不合适, 所以我们应该设定等待线程结束最大的时间 – 只要时间到, 不管来没来, 就不等了

  • join 能否被 interrupt 唤醒? --可以的-- 会自动清除中断标志位

【使用示例】

public class Test04 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {int cnt = 5;while (cnt-- != 0) {System.out.println("Hello join");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t thread end");});t.start();t.join();System.out.println("main thread end");}
}

2.5 线程休眠函数 -sleep()

  • 这是一个类方法, 调用的时候需要 Thread.sleep(long )
  • 参数是表示休眠 n 毫秒
  • 该休眠时间不是精确的, 例如: sleep(1000) – 该线程开始休眠 1000 ms, 1000 ms后, 该线程变成就绪状态, 时刻准备被CPU调度。但CPU调度也是需要时间的, 所以这其中就会出现误差
  • sleep的作用还有一个功能, 让当前线程主动放弃CPU资源; 利用这个特性, 可以 Thread.sleep(0) – 表示放弃啊CPU资源, 到就绪队列中等待CPU的重新调度。这个与 yield() 方法功能类似

三、线程状态

就绪状态、阻塞状态是系统设定的状态, Java对线程的状态的设定进行更进一步的细分.

3.1 观察所有线程的状态

线程状态是一个枚举类型 Thread.State

    public static void main(String[] args) {for(Thread.State x : Thread.State.values()) {System.out.println(x);}}
  • NEW: 安排了工作, 还未开始行动;
  • RUNNABLE: 可工作的, 又可以分成正在工作中和即将开始工作
  • BLOCKED: 表示队列排队等着其它事情 – 因为锁产生阻塞了
  • WATING: 表示队列排队等着其它事情 – 因为调用 wait 产生阻塞了
  • TIMED_WAITING: 表示队列排队等着其它事情 – 因为sleep 产生阻塞
  • TERMINATED: 工作完成了

3.2 线程状态和线程转移的意义

在这里插入图片描述
在这里插入图片描述

四、线程安全(重点)

4.1 观察线程不安全的现象

在这里插入图片描述
这里的结果不是预期的 100000!

4.2 线程安全的概念

  • 在多线程下,发现由于多线程执行,导致的 bug,统一成为“线程安全问题”,如果某个代码,在单线程下执行没有问题,多个线程下执行也没有问题,则成为“线程安全”,反之也可以称为“线程不安全”;

4.3 线程安全出现的原因

  1. [根本原因] 多个线程之间的调度顺序是"随机的", 操作系统使用"抢占式"执行的策略来调度线程, 罪魁祸首, 万恶资源
    • 和单线程不同的是, 多线程下, 代码的执行顺序, 产生了更多的变化, 以往只需要考虑代码在一个固定的顺序下执行, 执行正确即可, 现在则要考虑多线程下, N中执行顺序, 代码执行都得正确
    • 这件事情, 木已成舟, 无法改变, 当前主流的操作系统, 都是这样的抢占式 执行的
  2. 过个线程同时修改同一个变量, 容易产生线程安全问题 – 代码结构
    • 一个线程修改一个变量 – 没事
    • 多个线程同时读取同一变量 – 没事
    • 多个线程修改多个变量 – 没事
    • 多个线程同时修改同一个变量 – 危险
  3. 进行的修改, 不是 “原子的” – 解决线程安全主要的切入手段
    • 如果修改操作, 能够按照原子的方式来完成, 此时也不会有线程安全问题
  4. 内存可见性, 引起的线程安全问题
  5. 指令重排序, 引起的线程安全问题

解决线程安全的主要手段: 加锁, 将修改操作打包成原子性的

4.4 synchronized 关键字 --加锁手段

语法格式:

synchronized (一个对象) {// 代码块
}

作用 :

  1. 进入代码块就加锁
  2. 出了代码块就解锁

上述问题的解决如下:
在这里插入图片描述

  • 理解synchronized
    • synchronized 进行加锁解锁操作, 其实是以 “对象” 为维度进行展开的
    • 加锁的目的是为了互斥使用临界资源
    • 加锁规则如下:
      • 如果两个线程针对同一个对象进行加锁, 就会出现 锁竞争 / 锁冲突(一个线程能加锁成功, 另一个线程阻塞等待)
      • 如果两个线程针对的不同对象加锁, 不会产生锁竞争, 也就不会存在阻塞等待一系列的操作了
      • 具体是针对哪个对象加锁不重要, 重要的是两个线程是否是对同一个对象进行加锁!
    • synchronized 可以直接修饰方法
      • 如果修饰的普通成员方法 – 则加锁的对象就是 this 引用
      • 如果修饰的类方法 – 则加锁对象就是该类对象(.class) – 类用来实例化出对象的模板

4.5 volatile关键字

内存可见性引起的线程安全问题

问题现象指出

在这里插入图片描述

  • 问题出现:
    • 输入1, 在 main 线程中已经修改了 isQuit 的值, 但是 t 线程中没有看到isQuit 的值, 一直在循环, 没有退出
    • 不符合预期, 出现 bug , 是一个线程安全的问题
  • 问题分析:
    • 程序在编译运行的时候, Java 编辑器, 和 JVM 可能会对代码做出一些 “优化”
    • 程序员负责写代码的, 当写好一个代码之后, 人家开发 Java编辑器, 开发 JVM 的大佬, 可能会认为你这个代码写的不好, 当你的代码实际执行的时候, 编辑器/JVM 就可能把你的代码改了, 保持原有逻辑不变的情况下, 提供代码的执行效率
    • 编译器/JVM 优化的效果是非常明显的, 可以提高效率
    • 但是, 遇到了多线程, 此时的优化可能会出现差错
while (!isQuit) {}

该执行代码本质上有两个指令:

  1. load(读取内存) – 速度慢
  2. jcmp (比较并跳转) – 纯CPU, 速度贼快
  • 此时, 编译器/JVM就发现, 这个逻辑中, 代码要反复的, 快速的读取同一个内促的值, 并且, 这个内存的值, 每次读出来还是一样的!
  • 此时, 编译器就做出了一个大胆的决定, 直接 load 操作优化了, 只是执行第一次 load, 后续不在执行load, 直接拿寄存器中的数据进行比较了
  • 但是, 万万没想到, 程序员不讲武德, 搞偷袭, 在另一个线程中, 修改了 isQuit 的值!
  • 因为是通过scanner进行数据的输入, 编译器无法准确判断, main 线程 到底会不会修改 isQuit 的值, 啥时候执行, 因此就出现了误判
  • 所以虽然 main 线程把内存中的 isQuit 给改了, 但是另一个线程中, 并没有重复从内存中读取 isQuit 的值, 因此 t 线程就无法感知 main 的修改,也就出现了上述的问题

volatile 解决内存可见性的问题

  • 给要变的变量加上 volatile 关键字进行加以修饰, 说明该变量的是容易发生变化的, 就告诉编译器不要出现上述的优化, 每次都要从内存中读取变量的值
  • volatile 本质就是保证了变量的内存可见性
  • volatile 不保证原子性

在这里插入图片描述

编译器的优化是一种玄学, 为了保证代码的逻辑, 不能完全依靠编译器的优化

Java 中的内存模型

在这里插入图片描述

  • Java在主内存和CPU之间, 增加了一个 工作内存(也就是一个存储区)
  • 思考: CPU中有寄存器, 寄存器和主内存之间有1~3级的缓冲区, 为什么Java 对这些缓冲区叫做工作内存呢?
    • Java 要保证 “跨平台”
      • 支持不同的操作系统
      • 支持不同的硬件设备 (CPU)
    • 如果对每个硬件都需要特定的描述, 就会比较复杂, 因此官方直接发明了一个新的术语, “工作内存”, 同时也可以通过这个新的术语, 来屏蔽硬件的相关信息

五、线程同步

wait 和 notify

  • 多线程调度是随机的
  • 很多时候希望多个线程能够按照咱们自定的顺序来进行, 完成线程之间的配合工作
  • wait(等待) 和 notify(通知) 就是一个用来协调线程顺序的重要工具
  • 这两个方法, 都是 Object 提供的方法, 随便找个对象, 都可以使用wait 和 notify

wait

wait 在执行的时候, 会做三件事:

  1. 解锁 object,wait, 就会尝试针对 object 对象解锁
  2. 阻塞等待
  3. 当被其它线程唤醒之后, 就会尝试重新加锁, 加锁成功, wait 执行完毕, 继续往下执行其他逻辑
wait需要的注意事项
  • wait 要解锁, 就得先要加上锁
  • 所以, 先要synchronized给对象加上锁, 并在代码块里面使用wait方法等待

notify

  • 就是用来唤醒 在相应对象上 wait 的线程
  • wait 和 notify 都需要放到 synchronized 之内
    • 虽然 notify 不涉及 “解锁操作”, 但是 Java 也强制要求 notify 放到 synchronized中
  • 如果notify 的时候, 另一个线程没有处于 wait 状态, 此时notify相当于"孔大一炮", 不会有任何副作用

wait 和 notify 的控制线程的执行顺序, 解决线程饥饿的问题

wait 和 sleep 之间的区别

sleep 是有一个明确的时间, 到达时间, 自然会被唤醒, 也能提前唤醒, 使用 interrupt 就可以

wait 默认是一个死等, 一直等到有其他线程 notify . wait 也能够被 interrupt 提前唤醒.
- notify是顺理成章的唤醒, 唤醒之后该线程还需要继续工作, 后续还会进入到 wait 状态
- interrupt 告知线程要结束了, 接下来线程就要进入到收尾工作了

wait 也有一个带有超时间的版本(和 join 类似)

因此, 协调多个线程之间阿德执行顺序, 当然还是优先考虑使用 wait notify, 而不是 sleep

相关文章:

【JavaEE】Java的多线程编程基础知识 -- 多线程篇(2)

Java多线程编程基础知识 一、多线程的创建二、Thread类常用的方法和API2.1 Thread 的几个常见的属性2.2 start 启动一个线程2.3 终止一个线程2.4 等待一个线程-join()2.5 线程休眠函数 -sleep() 三、线程状态3.1 观察所有线程的状态3.2 线程状态和线程转移的意义 四、线程安全&…...

MFC Windows 程序设计[330]之表头控件例程(附源码)

MFC Windows 程序设计[330]之表头控件例程 程序之美前言主体运行效果核心代码逻辑分析结束语程序之美 前言 MFC是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含大量Wind…...

SettingsIntelligence

Android Settings 系列文章&#xff1a; Android Settings解析SettingsIntelligenceSettingsProvider 首语 Android Settings中搜索功能帮助我们可以快速访问设置项&#xff0c;进行自定义设置&#xff0c;以得到更佳的使用体验。Android Settings搜索的实现实际不在Setting…...

C#WPF Prism框架区域管理应用实例

本文实例演示C#WPFPrism框架区域管理应用实例 目录 一、Prism框架区域 二、不使用Prism框架的RegionManager 三、使用Prism框架的RegionManager 一、Prism框架区域...

LabVIEW基于机器视觉的钢轨表面缺陷检测系统

LabVIEW基于机器视觉的钢轨表面缺陷检测系统 机器视觉检测技术和LabVIEW软件程序&#xff0c;可以实现轨道工件的表面质量。CMOS彩色工业相机采集的图像通过图像预处理、图像阈值分割、形态分析、特征定位和图案匹配进行处理和分析。图形显示界面采用LabVIEW软件编程设计&…...

Qt程序的发布和打包,任何电脑都可以安装

## 1. Qt程序的发布 当Qt程序编写完成通过IDE编译就可以得到对应的可执行程序,这个可执行程序在本地运行是完全没有问题的(因为在本地有Qt环境,程序运行过程中可以加载到相关的动态库),但是如果我们想把这个Qt程序给到其他小伙伴使用可能就会出问题了,原因如下: 对方电…...

MD5生成和校验

MD5生成和校验 2021年8月19日席锦 任何类型的一个文件&#xff0c;它都只有一个MD5值&#xff0c;并且如果这个文件被修改过或者篡改过&#xff0c;它的MD5值也将改变。因此&#xff0c;我们会对比文件的MD5值&#xff0c;来校验文件是否是有被恶意篡改过。 什么是MD5&#xff…...

PostgreSQL 正则表达式匹配字段

在 PostgreSQL 数据库中&#xff0c;可以使用 ~ 和 !~ 操作符进行正则表达式的匹配和否定匹配。还可以使用 :: 操作符进行正则表达式的模式匹配。 例如&#xff0c;假设我们有一个名为 users 的表&#xff0c;其中有一个名为 email 的字段&#xff0c;我们可以使用以下 SQL 语句…...

关于iterm2的美化

iterm2 美化 笔者公司最近给发了一个新 M1 mac pro&#xff0c;所以一些软件需要重新安装。其中比较麻烦就是iterm2的一个美化工程 &#xff0c; 由于每次安装的效果都不尽相同所以这次写一个博客来记录一下 安装的过程 。 全程高能开始&#xff1a; 使用brew 来安装 iterm2 …...

Hook原理--逆向开发

今天我们将继续讲解逆向开发工程另一个重要内容--Hook原理讲解。Hook&#xff0c;可以中文译为“挂钩”或者“钩子”&#xff0c;逆向开发中改变程序运行的一种技术。按照如下过程进行讲解 Hook概述Hook技术方式fishhook原理及实例符号表查看函数名称总结 一、Hook概述 在逆…...

做数据可视化,谨记三大要点

数据可视化报表就是“一图胜千言”的最佳例子。数据可视化&#xff0c;也就是将数据图形化、图表化&#xff0c;以良好的视觉效果呈现数据&#xff0c;达到发现、分析、预测、监控、决策等目的。要想做出一份优秀的数据可视化报表&#xff0c;那就要在做报表时谨记三大要点&…...

软件设计原则-接口隔离原则讲解以及代码示例

接口隔离原则 一&#xff0c;介绍 1.前言 接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;ISP&#xff09;是面向对象设计中的一个原则&#xff0c;提倡使用多个专门的接口&#xff0c;而不使用单一的大接口。它最早由Robert C. Martin在其《敏捷软件…...

yolov8x-p2 实现 tensorrt 推理

简述 在最开始的yolov8提供的不同size的版本&#xff0c;包括n、s、m、l、x&#xff08;模型规模依次增大&#xff0c;通过depth, width, max_channels控制大小&#xff09;&#xff0c;这些都是通过P3、P4和P5提取图片特征&#xff1b; 正常的yolov8对象检测模型输出层是P3、…...

Type Script的变量类型

Typescript 的重要特性之一就是数据有类型了。 常见的类型如&#xff1a;字符串、数值、布尔等都有了明确的定义。 变量声明的格式 let 变量名:类型 初始值&#xff1b;字符型 let str:string "abc";数值型 数值型也支持不同的进制&#xff0c;用前缀区分 支持 整…...

系统架构师备考倒计时13天(每日知识点)

1. 数据仓库四大特点 面向主题的。操作型数据库的数据组织面向事务处理任务&#xff0c;各个业务系统之间各自分离&#xff0c;而数据仓库中的数据是按照一定的主题域进行组织的。集成的。数据仓库中的数据是在对原有分散的数据库数据抽取、清理的基础上经过系统加工、汇总和整…...

20 | Spring Data JPA 中文文档

Spring Data JPA 中文文档 1. 前言 Spring Data JPA 为 Jakarta Persistence API&#xff08;JPA&#xff09;提供 repository 支持。它简化了需要访问JPA数据源的应用程序的开发。 1.1. 项目元数据 版本控制: https://github.com/spring-projects/spring-data-jpaBug跟踪:…...

【AOA-VMD-LSTM分类故障诊断】基于阿基米德算法AOA优化变分模态分解VMD的长短期记忆网络LSTM分类算法(Matlab代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

K8s:Pod 中 command、args 与 Dockerfile 中 CMD、 ENTRYPOINT 的对应关系

写在前面 前几天被问到&#xff0c;这里整理笔记之前也没怎么注意这个问题理解不足小伙伴帮忙指正 曾以为老去是很遥远的事&#xff0c;突然发现年轻是很久以前的事了。时光好不经用&#xff0c;抬眼已是半生&#xff0c;所谓的中年危机&#xff0c;真正让人焦虑的不是孤单、不…...

Visual Studio Code (VS Code)安装教程

Visual Studio Code&#xff08;简称“VS Code”&#xff09;。 1.下载安装包 VS Code的官网&#xff1a; Visual Studio Code - Code Editing. Redefined 首先提及一下&#xff0c;vscode是不需要破解操作的&#xff1b; 第一步&#xff0c;看好版本&#xff0c;由于我的系…...

技巧 | 如何解决 zsh: permission denied 问题 | Mac

技巧 | 如何解决 zsh: permission denied 问题 | Mac 问题描述 在 macOS 系统终端执行 sh 程序脚本时&#xff0c;抛出异常 zsh: permission denied 原因分析 用户没有权限&#xff0c;所以才出现了这个错误&#xff0c;所以只需要用 chmod 修改一下权限就可以了 解决方法…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...