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

Synchronized与Java线程的关系

前言

​ Java多线程处理任务时,为了线程安全,通常会对共享资源进行加锁,拿到锁的线程才能进行访问共享资源。而加锁方式通过都是Synchronized锁或者Lock锁。

​ 那么多线程在协同工作的时候,线程状态的变化都与锁对象有关系。

Synchronized锁

​ Java采用synchronized关键字、以互斥同步的方式的解决线程安全问题。一般Synchronized主要用于同步代码块、实例方法、静态方法。

一、使用方式 字节码分析

// 使用
public synchronized void test1(){
}
public void test2(){synchronized(new Test()){}
}
public static synchronized void test3(){
}//反编译
public synchronized void test1();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZED    // herepublic void test2();descriptor: ()flags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: new           #2                  // class com/easy/helloworld/Test3: dup4: invokespecial #3                  // Method "<init>":()V7: dup8: astore_19: monitorenter                   // here10: aload_111: monitorexit                    // here12: goto          2015: astore_216: aload_117: monitorexit                    // here18: aload_219: athrow20: returnpublic static synchronized void test3();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED   // here

​ 由上可知,同步代码:通过moniterenter、moniterexit 关联到到一个monitor对象,进入时设置Owner为当前线程,计数+1、退出-1。除了正常出口的 monitorexit,还在异常处理代码里插入了 monitorexit。

​ 而实例方法、静态方式是隐式调用moniterenter、moniterexit。

二、Moniterenter、Moniterexit

​ monitorenter和monitorexit这两个jvm指令,主要是基于 Mark WordObject monitor来实现的。

​ 在 JVM 中,对象在内存中分为三块区域:

  • 对象头:由Mark WordKlass Point构成。

    • Mark Word(标记字段):用于存储对象自身的运行时数据,例如存储对象的HashCode,分代年龄、锁标志位等信息,是synchronized实现轻量级锁和偏向锁的关键。 64位JVM的Mark Word组成如下:

      img

    • Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据:这部分主要是存放类的数据信息,父类的信息。

  • 字节对齐:为了内存的IO性能,JVM要求对象起始地址必须是8字节的整数倍。对于不对齐的对象,需要填充数据进行对齐。

​ 在JDK 1.6之前,synchronized只有传统的锁机制,直接关联到monitor对象,存在性能上的瓶颈。在JDK 1.6后,为了提高锁的获取与释放效率,JVM引入了两种锁机制:偏向锁和轻量级锁。它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。这几种锁的实现和转换正是依靠对象头中的Mark Word

Synchronized锁机制

1、偏向锁

1)引入偏向锁的初衷

​ 在没有多线程竞争的情况下,尽量减少不必要的轻量级锁的执行。轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只依赖一次CAS原子指令。

​ 但在多线程竞争时,需要进行偏向锁撤销步骤,因此其撤销的开销必须小于节省下来的CAS开销,否则偏向锁并不能带来收益。JDK 1.6中默认开启偏向锁,可以通过-XX:-UseBiasedLocking来禁用偏向锁。在JDK15中,已经默认禁用偏向锁了。

2)关键字

prototype_header:JVM中的每个类有一个类似mark wordprototype_header用来标记该class的epoch和偏向开关等信息

匿名偏向状态:锁对象mark word标志位为101,且存储的Thread ID为空时的状态(即锁对象为偏向锁,且没有线程偏向于这个锁对象)。

Atomic::cmpxchg_ptrCAS函数。这个方法有三个参数,依次为exchange_valuedestcompare_value。如果dest的值为compare_value则更新为exchange_value,并返回compare_value。否则,不更新并返回实际原值

3)偏向锁流程

步骤 1、从当前线程的栈中找到一个空闲的Lock Record,并指向当前锁对象。

步骤 2、获取对象的markOop数据mark,即对象头的Mark Word;

步骤 3、判断锁对象的mark word是否是偏向模式,即低3位是否为101。若不是,进入步骤4。若是,计算anticipated_bias_locking_value,判断偏向状态:

步骤 3.1anticipated_bias_locking_value若为0,代表**偏向的线程是当前线程,且mark word的epoch等于class的epoch,**这种情况下直接执行同步代码块,什么都不用做。

步骤 3.2判断class的prototype_header是否为非偏向模式。若为非偏向模式,CAS尝试将对象恢复为无锁状态。无论cas是否成功都会进入轻量级锁逻辑。

步骤 3.3、**如果epoch偏向时间戳已过期,则需要重偏向。**利用CAS指令将锁对象的mark word替换为一个偏向当前线程且epoch为类的epoch的新的mark word

步骤 3.4CAS将偏向线程改为当前线程后,如果当前是匿名偏向(即对象头中的bit field存储的Thread ID为空)且无并发冲突,则能修改成功获取偏向锁,否则进入锁升级的逻辑。

步骤 4、走到一步会进行轻量级锁逻辑。构造一个无锁状态的mark word,然后存储到Lock Record

设置为无锁状态的原因是:轻量级锁解锁时是将对象头的mark wordcas替换为Lock Record中的Displaced Mark Word,所以设置为无锁状态。如果是锁重入,则将Lock RecordDisplaced Mark Word设置为null,放到栈帧中,起到计数作用。

注意

只有匿名偏向的对象才能进入偏向锁模式。JVM启动时会延时初始化偏向锁,默认是4000ms。初始化后会将所有加载的Klass的prototype header修改为匿名偏向样式。当创建一个对象时,会通过Klass的prototype_header来初始化该对象的对象头。

​ 简单的说,偏向锁初始化结束后,后续所有对象的对象头都为匿名偏向样式,在此之前创建的对象则为无锁状态。而对于无锁状态的锁对象,如果有竞争,会直接进入到轻量级锁。这也是为什么JVM启动前4秒对象会直接进入到轻量级锁的原因。

​ 那么为什么要延迟初始化?JVM启动时必不可免会有大量sync的操作,而偏向锁并不是都有利。如果开启了偏向锁,会发生大量锁撤销和锁升级操作,大大降低JVM启动效率。

4)偏向锁的撤销

偏向锁的 撤销(revoke)是一个很特殊的操作,为了执行撤销操作,需要等待全局安全点,此时所有的工作线程都停止了执行。

偏向锁的撤销操作并不是将对象恢复到无锁可偏向的状态,而是在偏向锁的获取过程中,发现可能存在竞争时,直接将一个被偏向的对象升级到被加了轻量级锁的状态。

场景说明:

​ 锁已经偏向线程A,此时线程B尝试获取锁。这种情况下会走到Mark标记的分支。

​ 如果需要撤销的是当前线程,只要遍历当前线程的栈就能拿到lock record,可以直接调用revoke_bias,不需要等到safe point再撤销。**在调用Object#hashcode时,也会走到该分支将为偏向锁的锁对象直接恢复为无锁状态。**若不是当前线程,会被push到VM Thread中等到safe point的时候再执行。

​ VMThread内部维护了一个VMOperationQueue类型的队列,用于保存内部提交的VM线程操作VM_operation。GC、偏向锁的撤销等操作都是在这里被执行。

撤销流程:

**步骤 1、查看偏向的线程是否存活,如果已经死亡,则直接撤销偏向锁。**JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。

步骤 2、偏向的线程是否还在同步块中,如果不在,则撤销偏向锁。如果在同步块中,执行步骤3。

​ 而是否在同步块的判断依据偏向锁的重入计数方式:在偏向锁的获取中,每次进入同步块的时候都会在栈中找到第一个可用(即栈中最高的)的Lock Record,将其obj字段指向锁对象。每次解锁的时候都会把最低的Lock Record移除掉,所以可以通过遍历线程栈中的Lock Record来判断是否还在同步块中。轻量级锁的重入也是基于Lock Record的计数来判断。

步骤 3、升级为轻量级锁。将偏向线程所有相关Lock RecordDisplaced Mark Word设置为null,再将最高位的Lock RecordDisplaced Mark Word 设置为无锁状态,然后将对象头指向最高位的Lock Record。这里没有用到CAS指令,因为是在safepoint,可以直接升级成轻量级锁。

小结

​ 当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。

​ 因此,JVM中增加了一种批量重偏向/撤销的机制以减少锁撤销的开销,而mark word中的epoch也是在这里被大量应用。但无论怎么优化,偏向锁的撤销仍有一定不可避免的成本。如果业务场景存在大量多线程竞争,那偏向锁的存在不仅不能提高性能,而且会导致性能下降。

总结

如果当前锁已偏向其他线程||epoch值过期||class偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到锁膨胀InterpreterRuntime::monitorenter方法, 在该方法中会进行偏向锁撤销和升级。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dzNki4f-1692344710003)(https://tech.youzan.com/content/images/2021/07/—.svg)]

2)轻量级锁

目的:

​ 在多线程交替执行同步块的情况下,尽量避免重量级锁使用的操作系统互斥量带来的开销。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。

​ 在偏向锁逻辑中,cas失败会执行到InterpreterRuntime::monitorenter。在轻量级锁逻辑中,如果当前线程不是轻量级锁的重入,也会执行到InterpreterRuntime::monitorenter

​ 其内部函数fast_enter的流程,主要逻辑为revoke_and_rebias:如果当前是偏向模式且偏向的线程还在使用锁,会将锁的mark word改为轻量级锁的状态,并将偏向的线程栈中的Lock Record修改为轻量级锁对应的形式(此时Lock Record是无锁状态),且返回值不是BIAS_REVOKED_AND_REBIASED,会继续执行slow_enter

slow_enter流程步骤

步骤 1markOop mark = obj->mark()方法获取对象的markOop数据mark;

步骤 2mark->is_neutral()方法判断mark是否为无锁状态,标识位001

步骤 3、如果mark处于无锁状态,把mark保存到BasicLock对象(Lock Record的属性)的displaced_header字段;

步骤 3.1、通过CAS尝试将Mark Word更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤4;

步骤 4、如果是重入,则设置Displaced Mark Word为null。

步骤 5、到这说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁;

轻量级锁的释放

​ 轻量级锁释放时需要将Displaced Mark Word替换回对象头的mark word中。如果CAS失败或者是重量级锁则进入到InterpreterRuntime::monitorexit方法中。monitorexit直接调用slow_exit方法释放Lock Record

最后执行的是如果是fast_exit方法,且是轻量级锁,尝试cas替换mark word。若解锁时有竞争,会调用inflate方法进行重量级锁膨胀,升级到到重量级锁后再执行exit方法。

3)重量级锁

​ 重量级锁通过对象内部的监视器(monitor)实现,其依赖于底层操作系统的Mutex Lock实现,需要额外的用户态到内核态切换的开销。由上文分析,slow_enter获取轻量级锁未成功时,会在inflate中完成锁膨胀。

​ 而inflate其中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:

1.已经是重量级状态,说明膨胀已经完成,返回并继续执行ObjectMonitor::enter方法。
2.如果是轻量级锁则需要进行膨胀操作。
3.如果是膨胀中状态,则进行忙等待。
4.如果是无锁状态则需要进行膨胀操作。

步骤过程

步骤 1、调用omAlloc获取一个可用的ObjectMonitor对象。在omAlloc方法中会先从线程私有monitor集合omFreeList中分配对象,如果omFreeList中已经没有monitor对象,则从JVM全局gFreeList中分配一批monitoromFreeList中;

步骤 2、通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中。如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成。

步骤 3、如果CAS成功,设置monitor的各个字段:设置monitor的header字段为displaced mark word,owner字段为Lock Record,obj字段为锁对象等;

步骤 4、设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象;

monitor竞争

​ 当锁膨胀inflate执行完并返回对应的ObjectMonitor时,并不表示该线程竞争到了锁,真正的锁竞争发生在ObjectMonitor::enter方法中。

monitor等待

ObjectMonitor竞争失败的线程,通过自旋执行ObjectMonitor::EnterI方法等待锁的释放。

EnterI大致原理

一个ObjectMonitor对象包括两个同步队列(_cxq_EntryList) ,以及一个等待队列_WaitSet。cxq、EntryList 、WaitSet都是由ObjectWaiter构成的链表结构。其中,_cxq为单向链表,_EntryList为双向链表。

​ 当一个线程尝试获得重量级锁且没有竞争到时,该线程会被封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程,进入BLOCKED状态。

​ 当线程释放锁时,会根据唤醒策略,从cxq或EntryList中挑选一个线程unpark唤醒。如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,进入WAITING或TIMED_WAITING状态。

​ 当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去,进入BLOCKED状态。需要注意的是,当调用一个锁对象的waitnotify方法时,若当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

monitor释放

​ 当某个持有锁的线程执行完同步代码块时,会进行锁的释放。在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor::exit方法中。

Java线程

1、线程的实现

1)内核线程实现

内核线程(Kernel-Level Thread,KLT):由内核来完成线程切换,内核通过调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。 程序一般不会直接去使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),也就是通常意义上的线程。

优点:每个LWP都是独立的调度单元。一个LWP被阻塞,不影响其他LWP。

缺点:基于KLT,耗资源。线程的创建、析构、同步都需要进行系统调用,频繁的用户态、内核态切换。

2)用户线程实现(User Thread,UT)

广义:非内核线程,都可认为是用户线程。(包括LWT,虽然LWT的大多操作都要映射到KLT)

狭义:完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。UT也只感知到掌管这些UT的进程P。

优点:用户线程的创建、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。

缺点:线程的创建、销毁、切换和调度都是用户必须考虑到问题。“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难。

3)混合模式。即存在用户线程,也存在轻量级进程

​ 用户线程的创建、切换、析构等操作依然廉价,可以支持大规模的用户线程并发,且可以使用内核线程提供的线程调度功能及处理器映射。

​ 线程的实现依赖操作系统支持的线程模型。在主流的操作系统上,hotspot、classic、art等虚拟机默认是 1:1的线程模型。在Solaris平台上,hotspot支持1:1、N:M两种线程模型。

2、线程的转换

​ 线程的状态,指的是Thread 类中threadStatus的值。

​ 创建Thread 类的对象,才能谈其状态。这个时候,线程t就处于新建状态。但他还不是“线程”。**调用start()后,会执行一个native方法创建内核线程。 这时候才有一个真正的线程创建出来,并即刻开始运行。这个内核线程与线程t进行1:1的映射。**这时候t具备运行能力,进入RUNNABLE状态。 RUNNABLE可以细分为READY和RUNNING,两者的区别只是是否等待到了资源并开始运行。

​ 处于RUNNABLE且未运行的线程,会进入一个就绪队列中,等待操作系统的调度。处于就绪队列的线程都在等待资源,这个资源可以是cpu的时间片、也可以是系统的IO。

3、线程相关方法

1)wait

​ 通过object获得objectMonitor,将Thread封装成OjectWaiter对象,然后addWaiter将它插入waitSet中,进入waiting或timed_waiting状态。最后释放锁,并通过底层的park方法挂起线程;

2)notify

​ 通过object获得objectMonitor,调用objectMonitor的notify方法。这个notify最后会走到ObjectMonitor::DequeueWaiter方法,获取waitSet列表中的第一个ObjectWaiter节点。并根据不同的策略,将取出来的ObjectWaiter节点,加入到EntryListcxq中。 notifyAll的实现类似于notify,主要差别在多了个for循环。

notifynotifyAll并不会立即释放所占有的ObjectMonitor对象,其真正释放ObjectMonitor的时间点是在执行monitorexit指令。

一旦释放ObjectMonitor对象了,entryListcxq中的ObjectWaiter节点会依据QMode所配置的策略,通过ExitEpilog方法唤醒取出来的ObjectWaiter节点。被唤醒的线程,继续参与monitor的竞争。若竞争失败,重新进入BLOCKED状态

3)join

join的本质仍然是 wait() 方法。在使用join时,JVM会帮我们隐式调用notify,因此我们不需要主动notify唤醒主线程。 而sleep()方法最终是调用SleepEvent对象的park方法

4)sleep

Thread.sleep在jvm层面上是调用thread中SleepEvent对象的park()方法实现阻塞线程,在此过程中会通过判断时间戳来决定线程的睡眠时间是否达到了指定的毫秒。

5)park

parkunpark方法也与同步语义无关。每个线程都与一个许可(permit)关联。unpark函数为线程提供permit,线程调用park函数则等待并消耗permit。

小结

​ 1、 JVM线程状态不代表内核线程状态。

​ 2、BLOCKED的线程一定处于entryList或cxq中,而处于WAITING和TIMED WAITING的线程,可能是由于执行了sleep或park进入该状态,不一定在waitSet中。也就是说,处于BLOCKED状态的线程一定是与同步相关。由这可延伸出,调用 jdk 的 lock并获取不到锁的线程,进入的是 WAITING 或 TIMED_WAITING 状态,而不是BLOCKED状态。

总结

在这里插入图片描述

相关文章:

Synchronized与Java线程的关系

前言 ​ Java多线程处理任务时&#xff0c;为了线程安全&#xff0c;通常会对共享资源进行加锁&#xff0c;拿到锁的线程才能进行访问共享资源。而加锁方式通过都是Synchronized锁或者Lock锁。 ​ 那么多线程在协同工作的时候&#xff0c;线程状态的变化都与锁对象有关系。 …...

使用本地电脑搭建可以远程访问的SFTP服务器

文章目录 1. 搭建SFTP服务器1.1 下载 freesshd 服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2. 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内网连接测试成功 3. 使用cpolar内网穿透3.1 创建SFTP隧道3.2 查看在线隧道列表 4. 使用SFTP客户端&#x…...

批量修改文件名怎么操作?

批量修改文件名怎么操作&#xff1f;不管你使用电脑处理工作还是进行学习&#xff0c;都会在电脑中产生很多的文件&#xff0c;时间一久电脑里的文件更加杂乱无章&#xff0c;这时候如果不对电脑中的文件进行及时的管理&#xff0c;那么很可能出现文件丢失而你自己还发现不了的…...

【LeetCode】538.把二叉搜索树转换为累加树

题目 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&#xff0c;二叉搜索树满足下列约束条件…...

linux 安装 kibana

首先下载 kibana https://www.elastic.co/cn/downloads/kibana 然后上传到linux /usr/local 目录下解压安装 修改config/kibana.yml 配置文件&#xff0c;将elasticsearch.hosts 然后再nginx 中做一个端口映射&#xff0c;实现在浏览器中输入后xxxx:5602 nginx 可以将请求转发…...

STM32入门——IIC通讯

江科大STM32学习记录 I2C通信 I2C&#xff08;Inter IC Bus&#xff09;是由Philips公司开发的一种通用数据总线两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09;同步&#xff0c;半双工带数据应答支持总线挂载多设备…...

DTC 19服务学习2

紧跟上篇 0x04 reportDTCSnapshotRecordByDTCNumber 通过DTC和快照序列来获取DTC快照记录。 适用以下假设&#xff1a; — 服务器支持存储给定 DTC 的两个 DTCSnapshot 记录的能力。 — 此示例假定是上一个示例的延续。 — 假设服务器请求服务器存储的 DTC 编号 123456 的两个…...

【腾讯云 TDSQL-C Serverless 产品体验】基于腾讯云轻量服务器以及 TDSQL-C 搭建 LNMP WordPress 博客系统

文章目录 一、前言二、数据库发展与云原生数据库2.1 数据库发展简介2.2 云原生数据库简介2.2.1 云数据库与云原生数据库区别 三、腾讯云 TDSQL-C 数据库3.1 什么是腾讯云 TDSQL-C 数据库3.2 为什么推出 TDSQL-C 数据库&#xff1f;传统 MySQL 架构存在较多痛点3.2.1 传统 MySQL…...

【vue3】对axios进行封装,方便更改路由并且可以改成局域网ip访问(附代码)

对axios封装是在main.js里面进行封装&#xff0c;因为main.js是一个vue项目的入口 步骤&#xff1a; 在1处创建一个axios实例为http&#xff0c;baseURL是基础地址&#xff08;根据自己的需求写&#xff09;&#xff0c;写了这个在vue界面调用后端接口时只用在post请求处写路由…...

Java IO流(三)线程模型

传统阻塞I/O模式 其中黄色框表示对象,蓝色框表示线程,白色框表示API方法 特点 采用阻塞IO模式获取输入数据每个连接都需要独立的线程完成数据的输入,业务处理和处理结果数据返回 潜在问题 并发数很大时,需要对应每个连接请求创建一个线程,所以占用资源很大连接创建后,若当前…...

string(模拟实现与深拷贝)

目录 深拷贝与浅拷贝 浅拷贝&#xff1a; 深拷贝 写时拷贝(了解) 模拟实现 准备 完整代码 深拷贝与浅拷贝 浅拷贝&#xff1a; 也称位拷贝&#xff0c;编译器只是将对象中的值拷贝过来。如果对象中管理资源&#xff0c;最后就会导致多个对象共享同一份资源&#xff0c;当一…...

5.Vue_Element

文章目录 1 Ajax1.1 Ajax介绍1.1.1 Ajax概述1.1.2 Ajax作用1.1.3 同步异步 1.2 Axios1.2.1 Axios的基本使用1.2.2 Axios请求方法的别名 2 前端工程化2.1 前端工程化特点2.2 Vue项目开发流程 3 Vue组件库Element3.1 Element介绍 1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 Ajax: 全…...

链路追踪jaeger

这里的链路指的是客户端向服务发起一个请求&#xff0c;该请求所经过的路线&#xff0c;也可以说是该请求经过的流量 例如&#xff1a; 客户端发起一个下订单的请求其流量过程&#xff1a; request—>service—>order-web—>order_srv—>mysql—>order_srv—&…...

神经网络基础-神经网络补充概念-42-梯度检验

概念 梯度检验&#xff08;Gradient Checking&#xff09;是一种验证数值计算梯度与解析计算梯度之间是否一致的技术&#xff0c;通常用于确保实现的反向传播算法正确性。在深度学习中&#xff0c;通过梯度检验可以帮助验证你的神经网络模型是否正确地计算了梯度&#xff0c;从…...

<kernel>kernel 6.4 USB-之-hub_port_connect()分析

&#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_port_connect()分析 kernel 6.4 USB系列文章如下&#xff1a; &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_event()分析 &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-port_event()分析 &#xff1c;kern…...

linux驱动学习3-外部中断

在做中断试验时&#xff0c;发现中断驱动总是insmod失败&#xff0c;之后定位到 gpio_request 失败&#xff0c;之后是想到使用的野火做好的系统&#xff0c;在uEnv.txt中会加载大量设备树插件&#xff0c;将key相关的设备树插件屏蔽即可。 linux中断API函数 中断号 每个中断…...

vue中的canvas插件

vue中canvas插件有vue-konva、vue-fabricjs、vue-canvas-effect、vue-chartjs和vue-threejs等。详细介绍&#xff1a;1、vue-konva是一个用于在Vue.js中使用Konva.js的插件&#xff0c;Konva.js是一个功能强大的HTML5 2D 渲染引擎&#xff0c;可以用于创建交互式的Canvas应用程…...

分享图片 | 快速浏览网页资源,批量保存、一键分享图片

前言 小伙伴学习吉他&#xff0c;有时需要在互联网搜索曲谱资源&#xff0c;而多数曲谱均为图片&#xff0c;并且为多页&#xff0c;在电脑上显示练习很不方便&#xff0c;需要停下来点击鼠标进行翻页&#xff0c;影响练习的连贯性。 为了解决上述问题&#xff0c;通常把图片…...

Programming abstractions in C阅读笔记:p123-p126

《Programming Abstractions In C》学习第50天&#xff0c;p123-p126&#xff0c;总结如下&#xff1a; 一、技术总结 1.notaion 这也是一个在计算机相关书籍中出现的词&#xff0c;但有时却不是那么好理解&#xff0c;因为它可以指代很多对象&#xff0c;这里做一个记录。示…...

自然语言处理从入门到应用——LangChain:链(Chains)-[通用功能:LLMChain、RouterChain和SequentialChain]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 LLMChain LLMChain是查询LLM对象最流行的方式之一。它使用提供的输入键值&#xff08;如果有的话&#xff0c;还包括内存键值&#xff09;格式化提示模板&#xff0c;将格式化的字符串传递给LLM&#xff0c;并返回LLM…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...