JUC高级九-Synchronized与锁升级

news2024/11/25 20:35:19

1. synchronized 锁优化的背景

  • 用锁能够实现数据的安全性,但是会带来性能下降
  • 无锁能够基于线程并行提升程序性能,但是会带来安全性下降

为求平衡将synchronized优化为不在是无所和重锁两个状态,新增偏向锁和轻量级锁来平衡安全性和性能问题

image-20230414113601002

synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略

image-20230409101546174

2. Synchronized的性能变化

2.1 java5以前-重量级锁

只有Synchronized,这个是操作系统级别的重量级操作,重量级锁,假如锁的竞争比较激烈的话,性能急剧下降

2.1.1 重量级锁为什么比较消耗性能?

重量级锁涉及到用户态内核态之间的切换

image-20230414115633794

java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的挂起线程和恢复线程都需要转入内核态去完成阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因

2.1.1.1 为什么每一个对象都可以成为一个锁?

C++源码: markOop.hpp

image-20230414142959610

Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

Monitor的本质是依赖于底层操作系统的==Mutex Lock==实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高。

2.1.1.2 Monitor与java对象以及线程是如何关联 ?

  1. 如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址
  2. Monitor的Owner字段会存放拥有相关联对象锁的线程id

2.1 Java 6之后优化Synchronized-引入了轻量级锁和偏向锁

  • Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁
  • 需要有个逐步升级的过程,别一开始就捅到重量级锁

3. synchronized锁种类及升级步骤

3.1 多线程访问情况,4种

根据这三种情况使用不同的锁

  1. 所有线程都可以来访问 ---->无锁
  2. 只有一个线程来访问,有且唯一Only One —>偏向锁
  3. 有2个线程A、B来交替访问 —>轻量锁
  4. 竞争激烈,多个线程来访问 —>重锁

3.1.1 锁升级总流程

3.1.1.1 锁的指向

  • 偏向锁:MarkWord存储的是偏向的线程ID
  • 轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针
  • 重量锁:MarkWord存储的是指向堆中的monitor对象的指针

BiasedLock

3.2 无锁

3.2.1 示例及对象头阅读规则

package site.zhourui.juc.synchronizedUpgrade;

import org.openjdk.jol.info.ClassLayout;

public class NoLock {
    public static void main(String[] args)
    {
        Object o = new Object();

        System.out.println("10进制hash码:"+o.hashCode());
        System.out.println("16进制hash码:"+Integer.toHexString(o.hashCode()));
        System.out.println("2进制hash码:"+Integer.toBinaryString(o.hashCode()));

        System.out.println( ClassLayout.parseInstance(o).toPrintable());
    }
}

执行结果:

锁标志位001代表无锁

image-20230414155254495

3.3 偏向锁 (白学警告-jdk15后将移除)

Hotspot 的作者经过研究发现,大多数情况下:

  • 多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况
  • 偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。
  • 在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程
  • 那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。
    如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,**直到竞争发生才释放锁。**以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
  • 假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的

3.3.1 主要作用

当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁

3.3.2 对象头中存储数据

  • 当前线程
    • 通过CAS方式修改markword中的线程ID
  • 分代年龄
  • 锁标志位—>101–>标志位理解就是无锁状态偏向锁标志位从0改为1

image-20230414155853909

3.3.3 技术实现

一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。

若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了。

个人理解:

  • 因为加锁需要用户态到内核态的转换,所以我们先在加锁前拦截一层如果每次都是同一线程来操作那么我们就不需要加锁,这样就没有用户态到内核态的转换
    • JVM不用和操作系统协商设置Mutex(争取内核),它只需要记录下线程ID就标示自己获得了当前锁,不用操作系统接入。
  • 而且Hotspot 的作者经过研究发现,大多数情况下:多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,所以这种情况很多
  • 除非第二个线程来争抢才会开始加锁升级为轻量级锁.
  • 偏向锁几乎没有额外开销,性能极高。

3.3.4 偏向锁JVM命令–查看JVM默认启动参数

3.3.4.1 Linux命令

windows上可以用git bash执行

java -XX:+PrintFlagsInitial |grep BiasedLock*

执行结果:

发现JVM启动时默认会启动偏向锁,但是会有4秒的延迟

所以需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。

image-20230414162128077

3.3.4.2 Windows命令

java -XX:+PrintFlagsInitial |find /i "BiasedLock"

执行结果和上面相同:

image-20230414162335763

3.3.5 开启/关闭偏向锁

3.3.5.1 开启

通过3.3.4我们发现:发现JVM启动时默认会启动偏向锁,但是会有4秒的延迟

因为UseBiasedLocking默认就等于true,只需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。

3.3.5.2 关闭

关闭偏向锁:关闭之后程序默认会直接进入 轻量级锁状态。

-XX:-UseBiasedLocking

3.3.5.3 示例

package site.zhourui.juc.synchronizedUpgrade;

import org.openjdk.jol.info.ClassLayout;

public class BiasedLockDemo {
    public static void main(String[] args)
    {
        Object o = new Object();

        new Thread(() -> {
            synchronized (o){
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            }
        },"t1").start();
    }
}
3.3.5.3.1 不添加任何参数

执行结果:

锁标志为000,表示程序启动时为轻量级锁

image-20230414163250439

3.3.5.3.2 关闭延迟-填入参数-XX:BiasedLockingStartupDelay=0

执行结果:

锁标志为101,表示程序启动时为偏向锁

image-20230414163551998

3.3.5.3.3 关闭偏向锁-填入参数-XX:-UseBiasedLocking

执行结果

锁标志为000,表示为轻量级锁

image-20230414164313087

3.3.5.3.4 不添加任何参数-当程序启动后睡5秒钟等偏向锁延时启动成功

image-20230414164644941

执行结果:

证实了延时启动

image-20230414164709293

3.3.6 对偏向锁线程ID的理解

偏向锁前54位为线程id

3.3.6.1 示例1-对象未用synchronized加锁

package site.zhourui.juc.synchronizedUpgrade;

import org.openjdk.jol.info.ClassLayout;

public class BiasedLockDemo2 {
    public static void main(String[] args)
    {
        try { Thread.sleep( 5000 ); } catch (InterruptedException e) { e.printStackTrace(); }
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

执行结果:

锁标志为101,为偏向锁但是前54位全是0没有任何信息

是因为o对象未用synchronized加锁,所以线程id为空.

image-20230414165318386

3.3.6.2 示例2-对象用synchronized加锁

结果参考3.3.5.3.2

发现线程id不再全是0

image-20230414163551998

3.3.7 偏向锁的撤销–开始有第2个线程来抢夺了

  • 偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。

  • 竞争线程尝试CAS更新对象头失败,**会等待到全局安全点(此时不会执行任何代码,类似JVM垃圾搜集的STW)撤销偏向锁。**同时检查持有偏向锁的线程是否还在执行

    image-20230414172026128

    1. 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级
      • 此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
    2. 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向 。

3.3.7.1 偏向锁升级与撤销流程–(红线部分)

请添加图片描述

3.3.8 偏向锁在Java15后逐步废除偏向锁

java15后就将不会默认开启偏向锁了

废除原因:

  1. 性能影响

    在过去,Java 应用通常使用的都是 HashTable、Vector 等比较老的集合库,这类集合库大量使用了 synchronized 来保证线程安全。

    如果在单线程的情景下使用这些集合库就会有不必要的加锁操作,从而导致性能下降。

    而偏向锁可以保证即使是使用了这些老的集合库,也不会产生很大的性能损耗,因为 JVM 知道访问临界区的线程始终是同一个,也就避免了加锁操作。

    这一切都很美好,但是随着时代的变化,新的 Java 应用基本都已经使用了无锁的集合库,比如 HashMap、ArrayList 等,这些集合库在单线程场景下比老的集合库性能更好。

    即使是在多线程场景下,Java 也提供了 ConcurrentHashMap、CopyOnWriteArrayList 等性能更好的线程安全的集合库。

    综上,对于使用了新类库的 Java 应用来说,偏向锁带来的收益已不如过去那么明显,而且在当下多线程应用越来越普遍的情况下,偏向锁带来的锁升级操作反而会影响应用的性能

  2. 代码侵入

    在废弃偏向锁的提案 JEP374 中还提到了与 HotSpot 相关的一点

    Biased locking introduced a lot of complex code into the synchronization subsystem and is invasive to other HotSpot components as well.

    简单翻译就是偏向锁为整个「同步子系统」引入了大量的复杂度,并且这些复杂度也入侵到了 HotSpot 的其它组件。

    这导致了系统代码难以理解,难以进行大的设计变更,降低了子系统的演进能力,

    总结下来其实就是 ROI (投资回报率)太低了,考虑到兼容性,所以决定先废弃该特性,最终的目标是移除它。

3.4 轻量级锁 -自旋锁(CAS)

image-20230414173941682

3.4.1 主要作用

  • 有线程来参与锁的竞争,但是获取锁的冲突时间极短
  • 本质就是自旋锁(CAS)
  • 轻量级锁是为了在线程近乎交替执行同步块时提高性能。
  • 主要目的: 在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋再阻塞
  • 升级时机: 当关闭偏向锁功能多线程竞争偏向锁会导致偏向锁升级为轻量级锁

3.4.2 升级为轻量级演示

参考3.3.5.3.3 关闭偏向锁-填入参数-XX:-UseBiasedLocking

image-20230414175145608

3.4.3 轻量级锁的加锁与释放

image-20230414175522737

3.4.3.1 轻量级锁的加锁

JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,官方成为Displaced Mark Word。若一个线程获得锁时发现是轻量级锁,会把锁的MarkWord复制到自己的Displaced Mark Word里面。然后线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针。

  • 如果成功,当前线程获得锁,
  • 如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

3.4.3.2 轻量级锁的释放

在释放锁时,当前线程会使用CAS操作将Displaced Mark Word的内容复制回锁的Mark Word里面。

  • 如果没有发生竞争,那么这个复制的操作会成功。
  • 如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻察的线程。

3.4.4 步骤流程图示–(红线部分)

Lightweight Lock

3.4.5 轻量级锁升级为重锁

3.4.5.1 升级条件–自旋达到一定次数和程度

3.4.5.1.1 java6之前–固定场景(了解)
  • 默认启用,默认情况下自旋的次数是 10 次
    • -XX:PreBlockSpin=10来修改
  • 或者自旋线程数超过cpu核数一半
3.4.5.1.2 Java6之后–自适应场景
  • 自适应意味着自旋的次数不是固定不变的
  • 而是根据:
    • 同一个锁上一次自旋的时间。
    • 拥有锁线程的状态来决定。

3.4.6 轻量锁与偏向锁的区别和不同

  • 争夺轻量级锁失败时,自旋尝试抢占锁
  • 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁

3.5 重锁

  • 有大量的线程参与锁的竞争,冲突性很高

  • Java中synchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。

  • 当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monior的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。

image-20230414180507276

3.5.1 示例

image-20230414180905842

4. 锁升级hashCode去那了?

锁升级为轻量级或重量级锁后,Mark Word中保存的分别是线程栈帧里的锁记录指针和重量级锁指针,已经没有位置再保存哈希码,GC年龄了,那么这些信息被移动到哪里去了呢?

image-20230414181642259

  • 在无锁状态下,Mark Word中可以存储对象的identity hash code值。当对象的hashCode()方法第一次被调用时,JVM会生成对应窋identity hash code值并将该值存储到Mark Word中。

  • 对于偏向锁,在线程获取偏向锁时,会用Thread ID和epoch值覆盖identity hash code所在的位置。**如果一个对象的hashCode()方法已经被调用过一次之后,这个对象不能被设置偏向锁。**因为如果可以的化,那Mark Word中的identity hash code必然会被偏向线程ld给覆盖,这就会造成同一个对象前后两次调用hashCode()方法得到的结果不一致。

    • 当一个对象已经计算过identity hashcode,它就无法进入偏向锁状态,跳过偏向锁,直接升级轻量级锁

      image-20230414182306820

    • 偏向锁过程中遇到一致性哈希计算请求,立马撤销偏向模式,膨胀为重量级锁

      image-20230414182444766

  • 升级为轻量级锁时JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,用于存储锁对象的Mark Word拷贝,该拷贝中可以包含identity hash code,所以轻量级锁可以和identity hash code共存,哈希码和GC年龄自然保存在此,释放锁后会将这些信息写回到对象头。

  • 升级为重量级锁后,Mark Word保存的重量级锁指针,代表重量级锁的Object Monitor类里有字段记录非加锁状态下的Mark Word,锁释放后也会将信息写回到对象头。

5. 各种锁优缺点、synchronized锁升级和实现原理

image-20230414182609810

6. JIT编译器对锁的优化

Just In Time Compiler,一般翻译为即时编译器

6.1 锁消除

锁消除:

从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用

package site.zhourui.juc.synchronizedUpgrade;
/**
 * 锁消除
 * 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,
 * 极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用
 */
public class LockClearUPDemo {
    static Object objectLock = new Object();//正常的

    public void m1()
    {
        //锁消除,JIT会无视它,synchronized(对象锁)不存在了。不正常的
        Object o = new Object();

        synchronized (o)
        {
            System.out.println("-----hello LockClearUPDemo"+"\t"+o.hashCode()+"\t"+objectLock.hashCode());
        }
    }

    public static void main(String[] args)
    {
        LockClearUPDemo demo = new LockClearUPDemo();

        for (int i = 1; i <=10; i++) {
            new Thread(() -> {
                demo.m1();
            },String.valueOf(i)).start();
        }
    }

}

执行结果:

其实就是每个线程使用的锁对象都不一样(每次加锁都是用的新new 的对象),只对一个线程加锁没事作用;

然后JIT编译器看到你这种脑残行为给你把锁去掉了:)

image-20230414182839312

6.2 锁粗化

锁粗化

  • 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能
package site.zhourui.juc.synchronizedUpgrade;

/**
 * 锁粗化
 * 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,
 * 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能
 */
public class LockBigDemo
{
    static Object objectLock = new Object();


    public static void main(String[] args)
    {
        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println("11111");
            }
            synchronized (objectLock) {
                System.out.println("22222");
            }
            synchronized (objectLock) {
                System.out.println("33333");
            }
        },"a").start();

        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println("44444");
            }
            synchronized (objectLock) {
                System.out.println("55555");
            }
            synchronized (objectLock) {
                System.out.println("66666");
            }
        },"b").start();

    }
}

执行结果:

前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能

image-20230414183401103

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

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

相关文章

【一起啃书】《机器学习》第四章决策树

第四章 决策树 4.1 基本流程 决策树是一类常见的机器学习方法&#xff0c;是基于树结构来进行决策的&#xff0c;通过对训练样本的分析来确定划分属性&#xff0c;来模拟人类决策过程。 一般的&#xff0c;一棵决策树包含一个根结点、若干个内部结点和若干个叶结点&#xff0c;…

分页查询工具类

package xyz.huanggy.minio.upload.Util; import java.util.ArrayList; import java.util.List; public class PageBean<T> {/*** 功能&#xff1a;分页工具类*/private int pageSize; // 每页记录数private int pageNo; // 当前页private int totalPages; // 总页数priv…

【Redis】Lua快速入门使用

【Redis】Lua快速入门使用 文章目录【Redis】Lua快速入门使用1. 概述2. 数据类型2.1 变量2.2 循环3. 函数4. 条件控制1. 概述 Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用…

vue+Nodejs+Koa搭建前后端系统(二)--koa-generator创建项目及分析

前言 采用上一篇vueNodejsKoa搭建前后端系统&#xff08;一&#xff09;–简易版创建的项目目录的基础上&#xff0c;创建新的后端服务项目server2使用koa-generator脚手架创建后端项目计算机系统为Windows 10 专业版 小说中&#xff0c;终成眷属一般就结局了&#xff0c;但现…

ESP32在ESP-IDF框架下为LVGL(v8.3)配置SD卡文件系统

踩坑记录 1、如果SD卡曾经做过系统盘&#xff08;比如说&#xff1a;作为树莓派的系统盘&#xff09;&#xff0c;那么要把系统盘的分区合并成一个&#xff08;这个网上有很多教程&#xff09;&#xff0c;并重新格式化&#xff0c;否则实验会失败。 2、并不是买回来的新的SD卡…

基于Vant组件库二次封装组件(TS+Vue3.x环境)

1. 今天的需求是封装一个 Navigation Bar 导航栏组件&#xff0c;目的是给到App几乎所有的页面复用&#xff1a; 2. 因为之前的项目里使用过Vant组件库&#xff0c;笔者第一时间想到了Vant组件库中的 NavBar 组件&#xff0c;和当前App的需求匹配度很高。Vant组件库的 NavBar 组…

压箱底教程分享,手把手教会你如何注册target账号和下单

喜欢套利的朋友肯定都认识target这个平台吧&#xff0c;它是美国热门的综合性海淘网站之一。东哥近日收到私信有朋友向我请教在注册target账号时遇到的一些问题&#xff0c;所以今天东哥想跟大家分享的就是就是target账号注册教程和下单流程&#xff0c;让也想注册target账号的…

软考第五章 无线通信网

无线通信网 无线通信网包括面向语音通信的移动电话系统以及面向数据传输的无线局域网和无线广域网。 WiFI底层是如何传输数据的呢 1.移动通信 1.1 蜂窝通信系统 1980年中期&#xff0c;欧洲和日本都建立了第一代蜂窝移动电话系统。蜂窝网络把一个地理区域划分成若干个称为…

Vue2-黑马(五)

目录&#xff1a; &#xff08;1&#xff09;vue2-组件重用 &#xff08;2&#xff09;vue2-element ui安装 &#xff08;3&#xff09;vue2-ElementUI-table &#xff08;4&#xff09;Element-ui-分页pagination &#xff08;1&#xff09;vue2-组件重用 页面上有很多的…

PyTorch中的符号索引和函数索引用法

Pytorch中很多函数都采用的是函数式索引的思路&#xff0c;而且使用函数式索引对代码可读性会有很大提升。 张量的符号索引 张量也是有序序列&#xff0c;我们可以根据每个元素在系统内的顺序位置&#xff0c;来找出特定的元素&#xff0c;也就是索引。 一维张量的索引 一维…

离线安装JumpServer

官网操作手册&#xff1a; https://docs.jumpserver.org/zh/v3/installation/setup_linux_standalone/offline_install/ 环境要求&#xff1a;&#xff08;内存最小需要4G&#xff09; 架构图 安装部署 1、下载 JumpServer官网下载&#xff1a; https://community.fit2cloud…

定点数的二进制表示形式

定点数的二进制表示形式 文章目录定点数的二进制表示形式什么是定点数表示格式数值范围与分辨率转换python 转换定点数C 双精度浮点数转换为8位和16位定点数C 将定点数转回浮点数测试什么是定点数 在嵌入式系统中&#xff0c;为了降低运算复杂度&#xff0c;通常还会使用定点数…

有趣的数学之回文数

“回文”是指正读反读都能读通的句子&#xff0c;它是古今中外都有的一种修辞方式和文字游戏&#xff0c;如“我为人人&#xff0c;人人为我”等&#xff0c;最有名的莫过于“上海自来水来自海上&#xff0c;人过大佛寺佛大过人 ”。你们知道吗&#xff0c;在数学中也有这样一类…

30多份软件测试报告模板,如何写一份优秀测试报告模板流程

相信很多做软件测试的小伙伴在软件测试后期&#xff0c;都为软件测试报告总结花费了很多的精力&#xff0c;那么如何做好软件测试报告呢&#xff1f;一份优秀的测试报告又包含哪些内容呢&#xff1f; 测试报告的核心要素 一、测试结论 从测试工程师的专业角度分析&#xff0…

pytorch进阶学习(四):使用不同分类模型进行数据训练(alexnet、resnet、vgg等)

课程资源&#xff1a;5、帮各位写好了十多个分类模型&#xff0c;直接运行即可【小学生都会的Pytorch】_哔哩哔哩_bilibili 目录 一、项目介绍 1. 数据集准备 2. 运行CreateDataset.py 3. 运行TrainModal.py 4. 如何切换显卡型号 二、代码 1. CreateDataset.py 2.Train…

如何基于ChatGPT+Avatar搭建24小时无人直播间

0 前言 最近朋友圈以及身边很多朋友都在研究GPT开发&#xff0c;做了各种各样的小工具小Demo&#xff0c;AI工具用起来是真的香&#xff01;在他们的影响下&#xff0c;我也继续捣鼓GPT Demo&#xff0c;希望更多的开发者加入一起多多交流。 上一篇结合即时通 IM SDK捣鼓了一个…

因为这三个面试题,我与字节offer失之交臂

我一个朋友挑战3个月入职字节&#xff0c;一路过关斩将直到终面&#xff0c;着实把我惊了一把&#xff0c;可惜的是&#xff0c;他倒在了最后三个面试题上。 我很讶异&#xff0c;前面不是打得很好吗&#xff1f;怎么会在最后几题上犯错误呢&#xff1f; 朋友说&#xff1a;别…

电瓶隔离器工作原理与发展简史

电瓶隔离器(Battery Isolators)工作原理与发展简史 电池隔离器(英文&#xff1a;Battery Isolators)&#xff0c;又叫双电池隔离器、双电瓶隔离器、双电瓶保护器&#xff0c;还有叫双电池分离器的。 电瓶隔离器其实并没有真正的隔离&#xff0c;负极是始终连在一起的。房车、…

拓展系统命令

文章目录拓展系统命令使用方式拓展系统命令快速运行方法命令 - ZFASTRUN安全运行方法命令 - ZFASTSAFERUN快速运行Query方法命令 - ZFASTQUERY安全运行Query方法 命令 - ZSAFEQUARY防止调试时误将数据提交命令 - ZTRN在Terminal执行SQL语句命令 - ZSQL安全Global命令 - ZSAFEKI…

动态内存管理【上篇】

文章目录⚙️1.为什么存在动态内存分配⚙️2.动态内存函数的介绍&#x1f4ec;2.1. malloc函数&#x1f4ec;2.2. free函数&#x1f4ec;2.3. calloc函数&#x1f4ec;2.4. realloc函数⚙️3.常见的动态内存错误&#x1f512;3.1.对NULL指针的解引用操作&#x1f512;3.2.对动态…