JavaEE之多线程的风险以及如何避免

上文我们了解了单线程以及线程的一些基本常见方法,但是多线程在一些方面会存在安全问题,此文我们来为多线程的安全 保驾护航!! 详情请见下文

1. 多线程带来的风险——线程安全

1.1 观察线程不安全

/*** 使用两个线程,让count自增到10w*/
public class Thread_lesson03_01 {//定义一个变量,让其自增10w次private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});thread1.start();Thread thread2=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});thread2.start();//等待线程结束thread1.join();;thread2.join();//结束后,打印计算的count值System.out.println("count="+count);}
}

在这里插入图片描述

1.2 线程安全的概念

想给出⼀个线程安全的确切定义是复杂的,
但我们可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的

1.3 线程不安全的原因

1. 线程调度是随机的

由于线程的执行顺序无法人为控制,抢占式执行是造成线程安全问题的主要原因而且我们解决不了,完全是CPU自己调度而且和CPU核数有关

2. 修改共享数据

  • 多个线程修改同一个变量,会出现线程安全问题
  • 多个线程修改不同的变量,不会出现线程安全问题
  • 一个线程修改一个变量,也不会出现线程安全问题

3. 原子性

我们在前面的MySQL学习过程中,知道了事务是有原子性的,比如对一个事务,如果不让他完全执行任务时,就对他操作,会造成幻读,不可读等等不好的效果,MySQL中我们通过隔离级别保证事务的原子性,原子性即 :事务要么全都执行,要么全都不执行
在线程中,我们知道⼀条Java语句不⼀定是原⼦的,也不⼀定只是⼀条指令
比如刚才我们看到的count++,其实是由三步操作组成的:

  1. 从内存把数据读到CPU (LOAD)
  2. 进行数据更新 (ADD)
  3. 把数据写回到CPU (STORE)

由于线程是抢占式执行的,此处通过时间线来助于理解线程的原子性:

在这里插入图片描述

在这里插入图片描述

下图,对线程的原子性助以理解:

在这里插入图片描述

由于执行CPU指令不是原子性的,导致这三条指令没有全部执行完成就被CPU调度走了
另外的线程加载到的值是一个原始值,当两个线程分别完成自增操作之后把值写回内存时发生了覆盖现象

4. 内存可见性

可见性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到.
Java虚拟机规范中定义了Java内存模型(JMM),如下图所示:
在这里插入图片描述

  1. 线程之间的共享变量存在主内存(MainMemory).
  2. 每⼀个线程都有自己的"⼯作内存"(WorkingMemory),且线程工作内存之间是隔离的,线程对共享变量的修改线程执行相互感知不到
  3. 当线程要读取⼀个共享变量的时候,会先把变量从主内存拷贝到⼯作内存,再从⼯作内存读取数据.
  4. 当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存.
  5. 工作内存是JAVA层面对物理层面的关于程序所使用到了寄存器的抽象
  6. 如果通过某种方式 让线程之间可以相互通信,称之为内存可见性

5.指令重排序

指令重排序概念:
重排序是编译器、JVM和CPU为了提高执行效率对指令顺序进行调整的现象。它在保证单线程语义不变的前提下,减少了读写操作,提升了程序运行速度,并且保证程序的运行结果是正确。重排序分为编译器优化、CPU重排序和内存系统的“重排序”。虽然带来性能提升,但也要注意其可能影响多线程环境中的数据一致性。

2. 如何解决线程不安全问题

1.线程的调度是随机执行的:硬件层面的,我们解决不了
2.修改共享数据:在真实业务场景中,很难避免多线程,提供效率,我们解决不了
3.原子性:我们可以通过🔒锁实现原子性,下文介绍
4.内存可见性:我们可以让进程之间通过一种通信关系,解决内存的不可见性
5.指令重排序:我们可以程序员直接指定那个先执行

能够解决 3、4、5 其中一项,我们的线程不安全问题就可以解决

3.synchronized 关键字

3.1 synchronized 的特性

synchronized 会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同⼀个对象synchronized就会阻塞等待.

  • 进⼊synchronized修饰的代码块,相当于加锁
  • 退出synchronized修饰的代码块,相当于解锁

在这里插入图片描述

synchronized⽤的锁是存在Java对象头⾥的。

在这里插入图片描述
可以粗略理解成,每个对象在内存中存储的时候,都存有⼀块内存表示当前的"锁定"状态(类似于厕所 的"有⼈/⽆⼈").

  • 如果当前是"⽆⼈"状态,那么就可以使⽤,使⽤时需要设为"有⼈"状态.
  • 如果当前是"有⼈"状态,那么其他⼈无法使用,只能排队

在这里插入图片描述
理解"阻塞等待"

针对每⼀把锁,操作系统内部都维护了⼀个等待队列.当这个锁被某个线程占有的时候,其他线程尝试进⾏加锁,就加不上了,就会阻塞等待,⼀直等到之前的线程解锁之后,由操作系统唤醒⼀个新的线程, 再来获取到这个锁.
注意:

  • 上⼀个线程解锁之后,下⼀个线程并不是⽴即就能获取到锁.⽽是要靠操作系统来"唤醒".这也就 是操作系统线程调度的⼀部分⼯作.
  • 假设有ABC三个线程,线程A先获取到锁,然后B尝试获取锁,然后C再尝试获取锁,此时B和C 都在阻塞队列中排队等待.但是当A释放锁之后,虽然B⽐C先来的,但是B不⼀定就能获取到锁, ⽽是和C重新竞争,并不遵守先来后到的规则.

3.2 synchronize关键字的魔力

我们通过下列代码来探究synchronize的魔力吧!


3.2.1 代码1:不添加synchronize关键字,查看计算结果

public class Thread_lesson04_01 {public static void main(String[] args) throws InterruptedException {//初始化累加对象Counter01 counter01=new Counter01();//创建两个线程对一个变量进时累加Thread thread1=new Thread(()->{counter01.increase();});Thread thread2=new Thread(()->{counter01.increase();});// 启动线程thread1.start();thread2.start();//等待线程thread1.join();thread2.join();System.out.println(counter01.count);}
}
class Counter01 {public int count=0;public void increase(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述


3.2.2 代码2:给increase方法加上synchronize关键字

public class Thread_lesson04_02 {public static void main(String[] args) throws InterruptedException {Counter02 counter=new Counter02();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter02 {public int count=0;public synchronized void increase(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述

此时,thread1线程先获取了锁,方法执行完成之后thread2线程再获取锁,这样的情况是一个单线程运行状态,是把多线程转换为一个单线程,从而解决线程安全问题,但并不是我们想要的效果


3.2.3 代码3:给increase方法中的代码块加上synchronize关键字

public class Thread_lesson04_03 {public static void main(String[] args) throws InterruptedException {Counter03 counter=new Counter03();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter03 {public int count=0;public  void increase(){//在真实业务中,在执行锁代码块之前有很多的数据获取或其他的可以并行执行的逻辑//1.从数据库中查询数据 selectAll()//2.对数据进行处理 build()//3.其他的不修改共享变量的方法//......//当执行到修改共享变量的逻辑时,再加锁//被锁修饰的代码块用{}包裹,其中()中可以是任何对象,使用this就是当前调用该方法的对象synchronized(this) {for (int i = 0; i < 50000; i++) {count++;}}}
}

在这里插入图片描述

虽然当前代码依旧是按串行执行了,但是在锁定的代码块前后,有其他的方法或代码可以进程执行


3.2.4 代码4:定义方法increase和方法increase1,increase方法用synchronize关键字修饰,线程1调用increase方法,线程2调用increase1方法

public class Thread_lesson04_04 {public static void main(String[] args) throws InterruptedException {Counter04 counter=new Counter04();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase1();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter04 {public int count=0;public synchronized void increase(){for (int i = 0; i < 50000; i++) {count++;}}public void increase1(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述

由于increase1方法不加锁,在对increase1方法方法执行时,increase1方法不需要锁即可执行,导致了线程不安全问题,具体情况如下:
在这里插入图片描述


总结 :关于synchronized
1.被 synchronized 修饰的代码会变成串行执行
2.synchronized 可以去修饰方法,也可以修饰代码块
3.被synchronized 修饰的代码并不是一次性在CPU上执行完,而是中途可能会被CPU调度走,但是只有当所有的指令执行完成之后才会释放锁
4.只给一个线程加锁,也会出现线程安全问题

了解完了synchronize关键字后,我们来谈谈何为锁,锁又有哪些知识呢??

4. 锁🔒

我们知道,事务的隔离级别是通过锁和MVCC机制保证的,那么Java当中锁是用什么来实现的呢?锁存放在哪里呢??

4.1 锁是如何解决线程安全问题的?

  1. 解决线程非原子性

通过synchronize给方法加锁解决了原子性问题

在这里插入图片描述

  1. 解决内存不可见性

后一个线程永远读到的是上一个线程存放到主内存的值,通过这样的方式实现了内存可见性,
并没有对内存可见性做技术上的处理

  1. 解决不了重排序问题

8.2 锁存放的位置

在Java虚拟机中,对象在内存中的结构可以划分为4个区域

  1. markword: 对象头:锁信息、GC(垃圾回收)次数,程序计数器,一般为8BYTE
  2. 类型指计:当时的对象是哪个类,一般为4BYTE
  3. 实例数据:成员变量,不定
  4. 对齐填充:一个对象所的占的内存必须是8byte的整数倍,根据实例数据确定

5. volatile 关键字

上面我们了解到引发线程不安全的问题有几种情况:

  • 线程的调度是随机的
  • 多个线程修改了共享数据
  • 原子性
  • 内存不可见性
  • 指令重排序

其中线程的调度是随机的,我们解决不了,对于共享数据的修改,我们使用了synchronize关键字对修改共享数据的代码进行加锁,实现了原子性,由于原子性让代码串行执行,间接的实现了内存的可见性,然而在真正意义上并没有对内存的不可见性做修改,Java中提供了volatile关键字,实现了内存可见性和禁止指令重排序,我们现在开始来揭开volatile神秘的面纱吧

volatile的作用: volatile 修饰的变量, 能够保证 “内存可见性”,也可以解决有序性的问题(禁止指令重排序)

5.1 内存可见性

代码在写入 volatile 修饰的变量的时候

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量的时候

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

前⾯我们讨论内存可见性时说了, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存),
速度非常快, 但是可能出现数据不⼀致的情况.
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了.

我们提供一段代码示例,来揭开volatile的魅力吧

在这个代码中

  • 创建两个线程 thread1 和 thread2
  • thread1中包含⼀个循环, 这个循环以 flag != 0 为循环条件.
  • thread2中从键盘读⼊⼀个非0整数, 并把这个整数赋值给 flag.
  • 预期当用户输入 !=0 的值的时候, thread1 线程结束.
public class Thread_lesson05_01 {static int flag=0;public static void main(String[] args) {Thread thread1= new Thread(()->{System.out.println("thread1线程启动...");while (flag==0) {//do nothing}System.out.println("thread1线程退出...");});//想要通过线程2对flag的值进行修改,让线程1停止Thread thread2= new Thread(()->{Scanner scanner = new Scanner(System.in);System.out.println("thread2线程启动...");System.out.println("输⼊⼀个非0整数:");flag=scanner.nextInt();});//启动线程thread1.start();thread2.start();}
}

在这里插入图片描述


为何thread2对flag值的修改thread1无法感知到呢??

thread1 读的是自己的工作内存(寄存器)中的内容.
当 thread2 对 flag 变量进行修改, 此时 thread1 感知不到 flag 的变化.

图解:

在这里插入图片描述

下面我们试着给flag加上volatile关键字修饰,查看效果:

static volatile int flag=0;

在这里插入图片描述

我们发现通过volatile关键字解决了内存不可见问题,那么volatile是究竟如何解决的呢?

我们知道为了解决内存的可见性问题,最重要的就是当一个线程修改了另一个线程需要的变量,必须让另一个线程感知到

在CPU层面:
在这里插入图片描述

在Java层面:

内存屏障
作用是保证指令执行的先后顺序从而保证内存可见性
在这里插入图片描述
volatile读:
在这里插入图片描述
volatile写:
在这里插入图片描述


5.2 有序性

用volatile 修饰过的变量,由于前后有内存屏障,保证了指令的执行顺序,也可以理解为告诉编译器不要进行指令重排,以此保证了有序性

5.3 volatile不能保证原子性

我们通过一段代码来测试volatile是否可以保证线程的原子性

public class Thread_lesson05_02 {static class Counter01{volatile int  count=0;void increase(){count++;}}public static void main(String[] args) throws InterruptedException {//初始化自增对象Counter01 counter01=new Counter01();//创建两个线程对一个变量进时累加Thread thread1=new Thread(()->{for (int i = 0; i < 50000; i++) {counter01.increase();}});Thread thread2=new Thread(()->{for (int i = 0; i < 50000; i++) {counter01.increase();}});// 启动线程thread1.start();thread2.start();//等待线程thread1.join();thread2.join();System.out.println("count="+counter01.count);}
}

在这里插入图片描述

结论volatile解决不了原子性问题

6. wait 和 notify

由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序,所以Java中的Object类给出了wait和notify方法

此时我们就有一个疑问了,前面我们不是学习过join方法也可以进行等待呀,为什么Java还要提供wait方法呢?

我们可以通过一个案列来理解:

一家人要吃包子 (主线程) ,妈妈让小明去买包子 (子线程),此时join主要等待子线程的结果,即一家人等着小明买回来的包子,当小明来到包子铺,有两种情况:

  1. 包子已经做好,小明可以直接买回家
  2. 老板正在做包子,此时小明就需要等待包子做好
    此时小明买包子这个线程和老板包包子这个线程没有主子线程之分,但是要等老板做包子这个线程的结果,这是就需要用wait(),当老板做好包子之后,大喊一声包子出锅了notify()

总结: join()用于主子线程的等待,wait()用于非主子线程的等待,wait()可以理解为当前线程等待另一个线程准备资源,当资源准备好之后,通过notify()通知当前线程

6.1 wait()方法

wait()/wait(long timeout): 让当前线程进入等待状态

wait 做的事情:

  • 使当前执行代码的线程进行等待.(把线程放到等待队列中)
  • 释放当前的锁
  • 满足⼀定条件时被唤醒,重新尝试获取这个锁.

wait要搭配synchronized来使用.脱离synchronized使用wait会直接抛出异常.

wait 结束等待的条件:

  • 其他线程调用该对象的notify方法.
  • wait等待时间超时(wait方法提供⼀个带有timeout参数的版本,来指定等待时间).
  • 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException 异常.

6.2 notify()方法

notify() 唤醒在当前对象上等待的线程.

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其
    它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈wait状态的线程。(并没有"先来后到")
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

6.3 notifyAll方法

notifyAll(): 唤醒在当前对象上等待的线程.
notify方法只是唤醒某⼀个等待线程.使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程.

6.4 演示wait() 和 notify()方法的使用

import java.util.concurrent.TimeUnit;/*** 演示wait() 和 notify()方法的使用* 创建两个线程,一个线程调用wait(),一个线程调用notify()*/
public class Thread_lesson05_03 {public static void main(String[] args) {//定义一个锁对象Object locker=new Object();//创建调用wait()线程Thread t1=new Thread(()->{synchronized (locker) {System.out.println("调用wait()之前...");// 执行线程的逻辑// 如果没有满足线程所需要的数据,那么就等待try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("wait()唤醒之后..");}});Thread t2 = new Thread(() -> {System.out.println("notify()之前...");// 同等待的锁对象进行唤醒synchronized (locker) {locker.notify();}System.out.println("notify()之后...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}});// 启动线程t1.start();t2.start();}
}

在这里插入图片描述
wati与notify 必须配置synchronized一起使用, 并且使用同一个锁对象

在这里插入图片描述

注意:

  1. 当一个线程调用了wait之后,就释放掉当前持有的锁,等待被其他的线程唤醒
  2. 当另一个线程调用了notify之后,之前调用了wait的线程被唤醒,需要重新去竞争锁,拿到锁之后,会从wait的位置继续执行逻辑

使用小结:

1. wait和notify必须搭配synchronized一起使用
2. wait和notify使用的锁对象必须是同一个
3. notify执行多少次都没有关系(即使没有线程在wait)

6.5 wait和sleep的区别

其实理论上wait和sleep完全是没有可比性的,因为

1. ⼀个是用于线程之间的通信的,⼀个是让线程阻塞⼀段时间
2. 唯⼀的相同点就是都可以让线程放弃执行一段时间
3. wait需要搭配synchronized使用,sleep,join不需要
4. wait是Object的方法,sleep是Thread的静态方法

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

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

相关文章

LoViT: 用于手术阶段识别的长视频Transformer|文献速递-生成式模型与transformer在医学影像中的应用

Title 题目 LoViT: Long Video Transformer for surgical phase recognition LoViT: 用于手术阶段识别的长视频Transformer 01 文献速递介绍 快速发展的手术数据科学&#xff08;SDS&#xff09;领域旨在通过先进利用手术室&#xff08;OR&#xff09;内医疗设备采集的数据…

pika:适用于大数据量持久化的类redis组件|简介及安装(一)

文章目录 0. 引言1. pika简介2. pika安装3. pika设置开机自启4. pika主从搭建5. pika哨兵模式实现自动容灾总结 0. 引言 最近因为公司中用到pika组件&#xff0c;于是将研究过程和理解进行系统记录&#xff0c;以供后续参考。 1. pika简介 pika是360开发的一款国产类redis的…

LNMP和Discuz论坛

文章目录 LNMP和Discuz论坛1 LNMP搭建1.1 编译安装nginx服务1.1.1 编译安装1.1.2 添加到系统服务 1.2 编译安装MySQL服务1.2.1 准备工作1.2.2 编辑配置文件1.2.3 设置路径环境变量1.2.4 数据库初始化1.2.5 添加mysqld系统服务1.2.6 修改mysql的登录密码 1.3 编译安装PHP服务1.3…

Yocto 项目运行超出 BitBake 范围的内容

尽管 Yocto 项目依赖 BitBake 解析和调度元数据来完成构建&#xff0c;但在实际开发中&#xff0c;可能需要执行超出 BitBake 直接管理范围的任务。例如&#xff0c;调用外部脚本或工具完成一些特定的处理逻辑&#xff0c;如生成配置文件、执行硬件初始化脚本或调用第三方构建工…

SpringBoot+OSS文件(图片))上传

SpringBoot整合OSS实现文件上传 以前,文件上传到本地(服务器,磁盘),文件多,大,会影响服务器性能 如何解决? 使用文件服务器单独存储这些文件,例如商业版–>七牛云存储,阿里云OSS,腾讯云cos等等 也可以自己搭建文件服务器(FastDFS,minio) 0 过程中需要实名认证 … 1 开…

生产慎用之调试日志对空间矢量数据批量插入的性能影响-以MybatisPlus为例

目录 前言 一、一些缘由 1、性能分析 二、插入方式调整 1、批量插入的实现 2、MP的批量插入实现 3、日志的配置 三、默认处理方式 1、基础程序代码 2、执行情况 四、提升调试日志等级 1、在logback中进行设置 2、提升后的效果 五、总结 前言 在现代软件开发中&#xff0c;性能优…

SQL 在线格式化 - 加菲工具

SQL 在线格式化 打开网站 加菲工具 选择“SQL 在线格式化” 或者直接访问 https://www.orcc.online/tools/sql 输入sql&#xff0c;点击上方的格式化按钮即可 输入框得到格式化后的sql结果

汇编语言学习

文章目录 前言机器语言与机器指令汇编语言与汇编指令用汇编语言编写程序的工作过程注意事项 计算机组成指令和数据的表示计算机中的存储单元计算机中的总线三类总线X86 CPU性能一览 CPU对存储器的读写内存地址空间将各类存储器看作一个逻辑存储器 —— 统一编址内存地址空间的分…

MATLAB深度学习(七)——ResNet残差网络

一、ResNet网络 ResNet是深度残差网络的简称。其核心思想就是在&#xff0c;每两个网络层之间加入一个残差连接&#xff0c;缓解深层网络中的梯度消失问题 二、残差结构 在多层神经网络模型里&#xff0c;设想一个包含诺干层自网络&#xff0c;子网络的函数用H(x)来表示&#x…

【PHP】部署和发布PHP网站到IIS服务器

欢迎来到《小5讲堂》 这是《PHP》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言安装PHP稳定版本线程安全版解压使用 PHP配置配置文件扩展文件路径…

SSM 校园一卡通密钥管理系统 PF 于校园图书借阅管理的安全保障

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装校园一卡通密钥管理系统软件来发挥其高效地信息处理的作用&a…

TCP 2

文章目录 Tcp状态三次握手四次挥手理解TIME WAIT状态 如上就是TCP连接管理部分 流量控制滑动窗口快重传 延迟应答原理 捎带应答总结TCP拥塞控制拥塞控制的策略 -- 每台识别主机拥塞的机器都要做 面向字节流和粘包问题tcp连接异常进程终止机器重启机器掉电/网线断开 Tcp状态 建…

【操作系统】实验二:观察Linux,使用proc文件系统

实验二 观察Linux&#xff0c;使用proc文件系统 实验目的&#xff1a;学习Linux内核、进程、存储和其他资源的一些重要特征。读/proc/stat文件&#xff0c;计算并显示系统CPU占用率和用户态CPU占用率。&#xff08;编写一个程序使用/proc机制获得以及修改机器的各种资源参数。…

【密码学】AES算法

一、AES算法介绍&#xff1a; AES&#xff08;Advanced Encryption Standard&#xff09;算法是一种广泛使用的对称密钥加密&#xff0c;由美国国家标准与技术研究院&#xff08;NIST&#xff09;于2001年发布。 AES是一种分组密码&#xff0c;支持128位、192位和256位三种不同…

【学习笔记】目前市面中手持激光雷达设备及参数汇总

手持激光雷达设备介绍 手持激光雷达设备是一种利用激光时间飞行原理来测量物体距离并构建三维模型的便携式高科技产品。它通过发射激光束并分析反射回来的激光信号&#xff0c;能够精确地获取物体的三维结构信息。这种设备以其高精度、适应各种光照环境的能力和便携性&#xf…

探索 LeNet-5:卷积神经网络的先驱与手写数字识别传奇

一、引言 在当今深度学习技术蓬勃发展的时代&#xff0c;各种复杂而强大的神经网络架构不断涌现&#xff0c;如 ResNet、VGG、Transformer 等&#xff0c;它们在图像识别、自然语言处理、语音识别等众多领域都取得了令人瞩目的成果。然而&#xff0c;当我们回顾深度学习的发展历…

【数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;编写一个程序实现链栈的基本运算。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 初始化栈、销毁栈、判断栈是否为空、进栈、出栈、取栈…

【笔记】架构上篇Day6 法则四:为什么要顺应技术的生命周期?

法则四&#xff1a;为什么要顺应技术的生命周期&#xff1f; 简介&#xff1a;包含模块一 架构师的六大生存法则-法则四&#xff1a;为什么要顺应技术的生命周期&#xff1f;&法则四&#xff1a;架构设计中怎么判断和利用技术趋势&#xff1f; 2024-08-29 17:30:07 你好&am…

Security自定义逻辑认证(极简案例)

项目结构 config SecurityConfig package com.wunaiieq.tmp2024121105.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.password.NoOpPasswordEnco…

docker安装ddns-go(外网连接局域网)

docker先下载镜像&#xff0c;目前最新版是v6.7.6 也可以csdn资源下载 再导入dockers https://download.csdn.net/download/u014756339/90096748 docker load -i ddns-go.tar 启动 docker run -d --name ddns-go --restartalways --nethost -v /opt/ddns-go:/root jeessy/…