Java中锁的全面详解(深刻理解各种锁)

一.Monitor

1. Java对象头

以32位虚拟机位例

对于普通对象,其对象头的存储结构为

总长为64位,也就是8个字节, 存在两个部分

  • Kclass Word: 其实也就是表示我们这个对象属于什么类型,也就是哪个类的对象.
  • 而对于Mark Word.查看一下它的结构存储

64位虚拟机中

而对于数组对象,我们将会多出32位来存储数组的长度.

2. Monitor原理

Monitor也就是管程,监视器,其实也就是我们熟知的锁.

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针.(只有重量级锁才会关联Monitor)

也就是如下图所示:

  • 每个对象都拥有一把锁,假设此时锁对象为Object.class对象.此时Thread-2拿到锁,会将自己的Mark Word指向对应的Monitor.也就是像这样(JDK6以前为重量级锁).此时会将自己原有的,例如之前的年龄age,hashcode等存储到Monitor中.

  • 其他线程来抢占锁,突然发现锁的Monitor中的Owner属性不为null.则进入阻塞状态.
  • 等Thread-2中代码执行完毕,将锁释放.Monitor将会唤醒所有阻塞的线程,并把之前存储的原有Object.class中对象头中的属性重新赋值给Object.class.此时其他线程就可以来抢占锁.(不公平的抢占)

注意:

  1. synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  2. 不加 synchronized 的对象不会关联监视器,不遵从以上规则

字节码角度分析:

对于这样一段代码

我们查看对应的字节码指令,以来理解这个过程.

以上便是sybchronized过去的原理.

二.synchronized优化原理

而在从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,

如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提升了 synchronized的性能.

1. 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。

创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

如果 cas 失败,有两种情况

  1. 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  2. 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

  • 成功,则解锁成功
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

总的来说就是:

1. 进行加锁操作时,jvm会判断是否已经是重量级锁,如果不是,则会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中

2. 复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘3’,否则执行‘4’

3. 更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此对象处于轻量级锁状态

4. 更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执行‘5’,否则执行‘6’

5. 表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,并指向Mark Word的锁对象,起到一个重入计数器的作用。

6. 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,则升级为重量级锁(会由另一个线程为锁对象申请Monitor,也就是接下来的锁膨胀)

2.可重入锁

synchronized可重入的实现原理(优化过后)

  1. 锁对象与计数器
    • 每个Java对象都有一个与之关联的监视器(Monitor)。这个监视器内部维护了一个计数器(通常称为recursions变量),用于记录当前线程获取该对象锁的次数。
    • 当线程首次进入synchronized修饰的代码块或方法时,会尝试获取对象的锁。如果锁未被占用(即计数器为0),则线程获取锁并将计数器设置为1。(与之前再创造一个Lock Record不同)
  1. 重入机制
    • 如果当前线程已经持有了该对象的锁(即该线程已经执行了synchronized修饰的代码块或方法),当再次尝试进入另一个由相同对象锁保护的synchronized代码块或方法时,JVM会检查持有锁的线程是否为当前线程。
    • 如果是,则JVM允许该线程再次获取锁,并将计数器的值加1,而不是让线程等待锁被释放。这个过程就是synchronized的可重入性。
  1. 锁的释放
    • 当线程退出synchronized修饰的代码块或方法时,JVM会自动将对象锁的计数器减1
    • 如果计数器减至0,则表示当前线程已经释放了该对象的锁,其他等待获取该锁的线程将有机会获取锁并进入相应的synchronized代码块或方法。

2. 锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁.

static Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块}
}
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

这时 Thread-1 加轻量级锁失败,进入锁膨胀流程

  • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  • 然后自己进入 Monitor 的 EntryList BLOCKED

  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

3. 自旋锁

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

自旋重试成功的情况

自旋重试失败的情况

注意:

  1. 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  2. 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  3. Java 7 之后不能控制是否开启自旋功能

4. 偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有.可以理解为,偏向锁就是JVM认为这把锁就是属于一个线程

static final Object obj = new Object();
public static void m1() {synchronized( obj ) {// 同步块 Am2();}
}
public static void m2() {synchronized( obj ) {// 同步块 Bm3();}
}
public static void m3() {synchronized( obj ) {}
}

4.1 偏向状态

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后3位,为101这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数

-XX:BiasedLockingStartupDelay=0 来禁用延迟

  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值

// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0 
public static void main(String[] args) throws IOException {Dog d = new Dog();ClassLayout classLayout = ClassLayout.parseInstance(d);new Thread(() -> {log.debug("synchronized 前");System.out.println(classLayout.toPrintableSimple(true));synchronized (d) {log.debug("synchronized 中");System.out.println(classLayout.toPrintableSimple(true));}log.debug("synchronized 后");System.out.println(classLayout.toPrintableSimple(true));}, "t1").start();
}

输出

11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

注意:

处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

测试禁用

在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁

输出

11:13:10.018 c.TestBiased [t1] - synchronized 前 
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
11:13:10.021 c.TestBiased [t1] - synchronized 中 
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
11:13:10.021 c.TestBiased [t1] - synchronized 后 
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
4.2 撤销 - 调用对象 hashCode

调用了对象的 hashCode, 但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

所以由于偏向锁没有存储空间来存储hashCode的值,只能撤销.(仔细查看偏量锁的对象头结构)

  • 在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking(禁用默认偏向锁.)

输出

11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015 
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
4.3 撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

private static void test2() throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}synchronized (TestBiased.class) {TestBiased.class.notify();}// 如果不用 wait/notify 使用 join 必须打开下面的注释// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的/*try {System.in.read();} catch (IOException e) {e.printStackTrace();}*/}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (TestBiased.class) {try {TestBiased.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}, "t2");t2.start();
}

输出

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
4.4 撤销 - 调用 wait/notify
public static void main(String[] args) throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));try {d.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t1");t1.start();new Thread(() -> {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (d) {log.debug("notify");d.notify();}}, "t2").start();
}

输出

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

4.5 批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的Thread ID .

当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得有可能偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程.

private static void test3() throws InterruptedException {Vector<Dog> list = new Vector<>();Thread t1 = new Thread(() -> {for (int i = 0; i < 30; i++) {Dog d = new Dog();list.add(d);synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}synchronized (list) {list.notify();} }, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (list) {try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("===============> ");for (int i = 0; i < 30; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t2");t2.start();
}

输出(只取对应了的部分)

[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101[t2] - ===============> (注意到19次的变化,没有撤销)
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
4.6 批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的.(第二个循环擦除了20次,第三个循环擦除了20次.这两个循环前20次都是擦除,然后锁重偏向,后19次都在锁偏向,即不会擦除)

private static void test4() throws InterruptedException {Vector<Dog> list = new Vector<>();int loopNumber = 39;t1 = new Thread(() -> {for (int i = 0; i < loopNumber; i++) {Dog d = new Dog();list.add(d);synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}LockSupport.unpark(t2);}, "t1");t1.start();t2 = new Thread(() -> {LockSupport.park();log.debug("===============> ");for (int i = 0; i < loopNumber; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}LockSupport.unpark(t3);}, "t2");t2.start();t3 = new Thread(() -> {LockSupport.park();log.debug("===============> ");for (int i = 0; i < loopNumber; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t3");t3.start();t3.join();log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

5. 锁消除

锁消除,Java虚拟机中的即时编译器会自动帮我们优化,不存在锁共享的情况会直接将锁擦除.

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

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

相关文章

基于python旅游景点满意度分析设计与实现

1.1研究背景与意义 1.1.1研究背景 随着旅游业的快速发展&#xff0c;满意度分析成为评估旅游景点质量和提升游客体验的重要手段。海口市作为中国的旅游城市之一&#xff0c;其旅游景点吸引了大量游客。然而&#xff0c;如何科学评估和提升海口市旅游景点的满意度&#xff0c;…

百度网盘Android一二面凉经(2024)

百度网盘Android一二面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《百度网盘Android一二面凉经(2024)》。 面试职位: 网盘主端研发组_Android高级研…

芯课堂 | SWM34S系列驱动TFT屏走线布局设计参考

​在TFT屏驱电路设计中&#xff0c;好的PCB板布局走线可以让调试更轻松&#xff0c;让EMC测试更容易通过&#xff0c;关于34S的PCB板设计应注意以下几点&#xff1a; 关于布局 在布局中需要尽量靠近MCU的元器件有&#xff1a;晶振电路所有元器件、复位电路RC、CAP滤波电容以及…

【AI大模型Agent探索】Qwen-Agent:基于Qwen的LLM应用开发框架

系列篇章&#x1f4a5; No.文章1【Qwen部署实战】探索Qwen-7B-Chat&#xff1a;阿里云大型语言模型的对话实践2【Qwen2部署实战】Qwen2初体验&#xff1a;用Transformers打造智能聊天机器人3【Qwen2部署实战】探索Qwen2-7B&#xff1a;通过FastApi框架实现API的部署与调用4【Q…

实战打靶集锦-31-monitoring

文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 ssh服务4.2 smtp服务4.3 http/https服务 5. 系统提权5.1 枚举系统信息5.2 枚举passwd文件5.3 枚举定时任务5.4 linpeas提权 6. 获取flag 靶机地址&#xff1a;https://download.vulnhub.com/monitoring/Monitoring.o…

Chapter12 屏幕后处理效果——Shader入门精要学习笔记

Chapter12 屏幕后处理效果 一、屏幕后处理概述以及基本脚本系统1.OnRenderImage 函数 —— 获取屏幕图像2.Graphics.Blit 函数 —— 使用特定的Shader处理3.在Unity中实现屏幕后处理的基本流程4.屏幕后处理基类 二、调整亮度、饱和度和对比度1.BrightnessSaturationAndContrast…

[计算机基础]一、计算机组成原理

计算机组成原理的考察目标为&#xff1a; 1. 掌握单处理器计算机系统中主要部件的工作原理、组成结构以及相互连接方式。 2. 掌握指令集体系结构的基本知识和基本实现方法&#xff0c;对计算机硬件相关问题进行分析&#xff0c;并能够对相关部件进行设计。 3. 理解计算机系统的…

001、Mac系统上Stable Diffusion WebUI环境搭建

一、目标 如标题所述&#xff0c;在苹果电脑&#xff08;Mac&#xff09;上搭建一套Stable Diffusion本地服务&#xff0c;以实现本地AI生图目的。 二、安装步骤 1、准备源码【等价于准备软件】 # 安装一系列工具库&#xff0c;包括cmake,protobuf,rust,python3.10,git,wge…

C#学习-刘铁猛

文章目录 1.委托委托的具体使用-魔板方法回调方法【好莱坞方法】&#xff1a;通过委托类型的参数&#xff0c;传入主调方法的被调用方法&#xff0c;主调方法可以根据自己的逻辑决定调用这个方法还是不调用这个方法。【演员只用接听电话&#xff0c;如果通过&#xff0c;导演会…

请你谈谈:spring bean的生命周期 - 阶段2:Bean实例化阶段

在Spring框架中&#xff0c;Bean的实例化是Bean生命周期中的一个重要阶段。这个过程包括两个关键的子阶段&#xff1a;Bean实例化前阶段和Bean实例化阶段本身。 BeanFactoryPostProcessor&#xff1a;BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点&#xff0…

智慧城市遐想

很少有人会考虑自己居住的地方。我的意思是真正考虑效率、经济、美观和标准。我们甚至很少抬起头,将目光从智能手机上移开,去欣赏风景或享受城市风光。而且通常这是有道理的。 往日之城 过去,城市建在水源旁,距离河流或湖泊不超过一英里,这样才可以供水。现在,夜间飞越…

海豚调度器(DolphinScheduler)集群搭建详细笔记

海豚调度器集群搭建笔记 1.DolphinScheduler Cluster部署1.1 集群部署规划1.2 集群准备工作1.3 初始化数据库1.4 修改安装环境配置1.5 安装DolphinScheduler1.6 启停命令1.7 登录 DolphinScheduler UI 1.DolphinScheduler Cluster部署 分布式去中心化易扩展的工作流任务调度系…

中国机器视觉行业上市公司市场竞争格局分析

中国机器视觉产业上市公司汇总&#xff1a;分布在各产业链环节 机器视觉就是用机器来代替人眼做测量和判断的系统&#xff0c;机器检测相较于人工视觉检测优势明显。目前&#xff0c;我国机器视觉产业的上市公司数量较多&#xff0c;分布在各产业链环节。具体包括&#xff1a;…

【BES2500x系列 -- RTX5操作系统】深入探索CMSIS-RTOS RTX -- 配置篇 -- flash的使用 --(八)

&#x1f48c; 所属专栏&#xff1a;【BES2500x系列】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f49…

ClickHouse集成LDAP实现简单的用户认证

1.这里我的ldap安装的是docker版的 docker安装的化就yum就好了 sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl start docker 使用下面的命令验证sudo docker run hello-world docker pull osixia/openl…

RK3568笔记三十九:多个LED驱动开发测试(设备树)

若该文为原创文章&#xff0c;转载请注明原文出处。 通过设备树配置一个节点下两个子节点控制两个IO口&#xff0c;一个板载LED&#xff0c;一个外接LED。 一、介绍 通过学习设备树控制GPIO&#xff0c;发现有多种方式 一、直接通过寄存器控制 二、通过设备树&#xff0c;但…

windows和linux的等保加固测评的经验分享

一头等保加固测评的牛马&#xff0c;需要能做到一下午测评n个服务器 接下来就讲讲如何当一头xxxxxxxxx》严肃的等保测评加固的经验分享&#xff08; 一、window等保 首先你要自己按着教程在虚拟机做过一遍&#xff08;win2012和win2008都做过一遍&#xff0c;大概windows的…

MATLAB科研数据可视化教程

原文链接&#xff1a;MATLAB科研数据可视化https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247609462&idx3&snf7043936fc5ee42b833c7c9f3bcd24ba&chksmfa826d91cdf5e4872eb275e5319b66ba6927ea0074fb2293fe1ca47d6aedf38ab91050be484c&token1551213…

【大模型】FAISS向量数据库记录:从基础搭建到实战操作

文章目录 文章简介Embedding模型BGE-M3 模型亮点 FAISS是什么FAISS实战安装faiss加载Embedding模型创建FAISS数据库搜索FAISS数据删除FAISS数据保存、加载FAISS索引 总结 本人数据分析领域的从业者&#xff0c;拥有专业背景和能力&#xff0c;可以为您的数据采集、数据挖掘和数…

1.厦门面试

1.Vue的生命周期阶段 vue生命周期分为四个阶段 第一阶段&#xff08;创建阶段&#xff09;&#xff1a;beforeCreate&#xff0c;created 第二阶段&#xff08;挂载阶段&#xff09;&#xff1a;beforeMount&#xff08;render&#xff09;&#xff0c;mounted 第三阶段&#…