并发编程-Synchronized介绍(结合源码和hostpot说明)

news2024/9/30 23:26:21

目录

一、Synchronized 概述

二、Synchronized在并发编程中解决的问题

2.1 解决原子性问题

2.1.1 问题代码

2.1.2 执行结果

2.1.3 优化代码

2.1.4 测试结果

2.1.5 优化代码分析

2.1.5.1 编译java源文件程序

2.1.5.2 查看编译文件

2.1.5.3 分析编译文件

2.2 解决可见性问题

2.2.1 问题代码

2.2.2 执行结果

2.2.3 优化代码

2.2.4 测试结果

2.2.5 优化代码分析

2.2.5.1 synchronized 修饰方法

2.2.5.1.1 源代码

2.2.5.1.2 执行结果

2.2.5.1.3 编译分析

2.2.5.2 synchronized 修饰代码块

2.2.5.2.1 代码

2.2.5.2.2 执行结果

2.2.5.2.3 编译分析

三、synchronized 底层原理

3.1 monitor 监视器

3.1.1 monitor 来源

3.1.2 对象头

3.1.2.1 对象头的内存布局

3.1.2.2 对象头底层hotspot内存结构

3.1.2.2.1 instanceOopDesc

3.1.2.3 对象头底层hotspot数据结构

3.1.2.3.1 Mark word

3.1.2.3.2 klass pointer

3.1.2.3.3 实例数据

3.1.2.3.4 对齐填充

3.1.2.3.5 ObjectMonitor 数据结构

3.1.3 monitor 对象锁原理

3.1.3.1 执行流程图

3.1.3.2 执行流程说明

3.1.4 总结

四、Synchronized优化

4.1 锁粗化

4.1.1 定义

4.1.2 代码示意

4.2 锁消除

4.2.1 定义

4.2.2 示意代码

4.3 锁升级(锁膨胀)

4.3.1 定义

4.3.2 锁升级过程


一、Synchronized 概述

synchronized 是Java 1.5之后引进的一个解决并发编程中原子性、可见性这两个并发特性问题的解决方案,它是Java中基于对象锁实现的并发编程同步关键字,今天我们就结合代码一起看下它是如何解决原子性、可见性问题的,以及它底层的实现原理是什么,同时看下Java 1.6之后,对它的优化措施是什么。

二、Synchronized在并发编程中解决的问题

2.1 解决原子性问题

2.1.1 问题代码

package com.ningzhaosheng.thread.concurrency.features.atom;

/**
 * @author ningzhaosheng
 * @date 2024/2/5 18:33:27
 * @description 原子性测试
 */
public class TestAtom {
    private static int count;
    // ++操作自增
    public static void increment(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}


2.1.2 执行结果

可见,以上执行结果在多线程环境下,多线程操作共享数据时,预期的结果,与最终执行的结果不符。

通过分析可知,其实count++操作,并不是一个原子性操作,它包含了getstatic、iconst_1、iadd、putstatic四个操作步骤,在多线程执行的过程中,会出现并发问题。

2.1.3 优化代码

package com.ningzhaosheng.thread.concurrency.features.atom.syn;

/**
 * @author ningzhaosheng
 * @date 2024/2/6 19:14:17
 * @description 测试synchronized保证原子性
 */
public class TestSynchronized {
    private static int count;

    // ++操作自增
    public static void increment() {
        synchronized (TestSynchronized.class) {
            count++;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}


2.1.4 测试结果

我们发现,使用synchronized关键字后,在多线程并发的情况下,执行结果和预期值一致,没有了并发问题。

2.1.5 优化代码分析

为什么使用的synchronized之后,能解决由于原子性问题导致的并发问题呢?要回答这个问题,我们还需要编译代码,看下字节码,看到底synchronized做了些什么操作。

2.1.5.1 编译java源文件程序

2.1.5.2 查看编译文件
javap -v .\TestSynchronized.class

2.1.5.3 分析编译文件

我们可以通过分析编译出来的.class字节码文件,分析添加了synchronized关键字后,做了些什么操作。

通过上图中的字节码我们可以看到,添加synchronized关键字后,在执行count++操作的getstatic、iconst_1、iadd、putstatic等四个操作步骤指令的前后位置分别添加了monitorenter、monitorexit两个线程同步指令。monitorenter指令能使线程获得对象监视器(其实就是对象锁)。monitorexit指令释放并退出对象监视器(其实就是对象锁)。这两个指令的使用能避免多线程同时操作临街资源,并保证同一时间点,只会有一个线程正在操作临界资源。从而避免了并发安全问题。

2.2 解决可见性问题

2.2.1 问题代码

package com.ningzhaosheng.thread.concurrency.features.visible;

/**
 * @author ningzhaosheng
 * @date 2024/2/5 19:36:39
 * @description 测试可见性
 */
public class TestVisible {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (flag) {
                // ....
            }
            System.out.println("t1线程结束");
        });

        t1.start();
        Thread.sleep(10);
        flag = false;
        System.out.println("主线程将flag改为false");
    }
}


2.2.2 执行结果

由结果可知,主线程修改了flag = false;但是并没有使t1线程里面的循环结束.

2.2.3 优化代码

package com.ningzhaosheng.thread.concurrency.features.visible.syn;

/**
 * @author ningzhaosheng
 * @date 2024/2/5 19:52:31
 * @description 测试synchronized
 */
public class TestSynchronized {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (flag) {
                synchronized (TestSynchronized.class) {
                    //...
                }
                System.out.println(111);
            }
            System.out.println("t1线程结束");

        });

        t1.start();
        Thread.sleep(10);
        flag = false;
        System.out.println("主线程将flag改为false");
    }
}


2.2.4 测试结果

从测试结果可以看出,使用了synchronized同步代码块之后,在主线程中修改了flag=false 之后,线程t1也获取到最新的变量值,结束了while循环。也就是说synchronized也可以解决并发编程的可见性问题。那么synchronized是怎么保证并发编程的可见性的呢,我们接下来分析下。

2.2.5 优化代码分析

2.2.5.1 synchronized 修饰方法
2.2.5.1.1 源代码
package com.ningzhaosheng.thread.concurrency.features.visible.syn;

/**
 * @author ningzhaosheng
 * @date 2024/2/13 10:16:36
 * @description synchronized 修饰方法
 */
public class TestSynchronizedMethod {
    public static boolean flag = true;

    public static synchronized void runwhile() {
        while (flag) {
            System.out.println(111);
        }
        System.out.println("t1线程结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {

            runwhile();

        });
        t1.start();
        Thread.sleep(10);
        flag = false;
        System.out.println("主线程将flag改为false");
    }
}

2.2.5.1.2 执行结果

2.2.5.1.3 编译分析
javap -v .\TestSynchronizedMethod.class

可以看见,使用synchronized修饰方法后,通过javap -v 查看编译的字节码,会生成一个ACC_SYNCHRONIZED标识符,会隐式调用monitorenter和monitorexit。在执行同步方法前会调用monitorenter,在执行完同步方法后会调用monitorexit。

可查看官网解析:

Chapter 2. The Structure of the Java Virtual Machine (oracle.com)

该标识符的作用是使当前线程优先获取Monitor对象,同一个时刻只能有一个线程获取到,在当前线程释放Monitor对象之前,其它线程无法获取到同一个Monitor对象,从而保证了同一时刻只能有一个线程进入到被synchornized修饰的方法。

获取到锁资源之后,会将内部涉及到的变量从CPU缓存中移除,且要求线程必须去主内存中重新拿数据,在释放锁之后,会立即将CPU缓存中的数据同步到主内存。

2.2.5.2 synchronized 修饰代码块
2.2.5.2.1 代码
package com.ningzhaosheng.thread.concurrency.features.visible.syn;

/**
 * @author ningzhaosheng
 * @date 2024/2/13 10:48:02
 * @description synchronized 修饰代码块
 */
public class TestSynchronizedCodeBlock {
    public static boolean flag = true;

    public static void runwhile() {
        while (flag) {
            synchronized (TestSynchronizedCodeBlock.class) {
                System.out.println(flag);
            }
            System.out.println(111);
        }
        System.out.println("t1线程结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {

            runwhile();

        });
        t1.start();
        Thread.sleep(10);
        flag = false;
        System.out.println("主线程将flag改为false");
    }
}

2.2.5.2.2 执行结果

2.2.5.2.3 编译分析
javap -v .\TestSynchronizedCodeBlock.class

可以看到,使用synchronized修饰代码块后,查看编译的字节码会发现再存取操作静态共享变量时,会插入monitorenter、monitorexit原语指令,关于这两个指令的说明,可查看文档:

Chapter 6. The Java Virtual Machine Instruction Set (oracle.com)

它实现可见性的原理:

当前线程优先获取Monitor对象,同一个时刻只能有一个线程获取到,在当前线程释放Monitor对象之前,其它线程无法获取到同一个Monitor对象,从而保证了同一时刻只能有一个线程进入到被synchornized修饰的代码块。

获取到锁资源之后,会将内部涉及到的变量从CPU缓存中移除,且要求线程必须去主内存中重新拿数据,在释放锁之后,会立即将CPU缓存中的数据同步到主内存。

三、synchronized 底层原理

从上面分析结果可以看出无论是synchronized代码块还是synchronized方法,其线程安全的语义实现最终依赖一个叫monitor的东西,那么这个神秘的东西是什么呢,我们一起来研究下。

3.1 monitor 监视器

3.1.1 monitor 来源

当一个线程尝试访问synchronized修饰的代码块时,它首先要获得锁,那么这个锁到底存在哪里呢?是存在锁对象的对象头中的。

Monitor锁是基于操作系统的Mutex锁实现的,Mutex锁是操作系统级别的重量级锁,所以性能较低。

在Java中,创建的任何一个对象在JVM中都会关联一个Monitor对象,所以说任何一个对象都可以成为锁。

3.1.2 对象头

3.1.2.1 对象头的内存布局

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下图所示:

3.1.2.2 对象头底层hotspot内存结构

HotSpot采用instanceOopDesc和arrayOopDesc来描述对象头,arrayOopDesc对象用来描述数组类型。

3.1.2.2.1 instanceOopDesc

instanceOopDesc的定义的在Hotspot源码的 instanceOop.hpp 文件中,具体内容如下:

3.1.2.2.2 arrayOopDesc

arrayOopDesc的定义对应 arrayOop.hpp ,具体内容如下:

3.1.2.3 对象头底层hotspot数据结构

在普通实例对象中, oopDesc的定义包含两个成员,分别是 _mark 和 _metadata。

  • _mark 表示对象标记、属于markOop类型,也就是接下来要讲解的Mark World,它记录了对象和锁有关的信息。
  • _metadata 表示类元信息,类元信息存储的是对象指向它的类元数据(Klass)的首地址,其中Klass表示普通指针、 _compressed_klass 表示压缩类指针。

对象头由两部分组成,一部分用于存储自身的运行时数据,称之为 Mark Word,另外一部分是类型指针,及对象指向它的类元数据的指针。

3.1.2.3.1 Mark word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Mark Word对应的类型是 markOop 。源码位于 markOop.hpp 中。

jdk8u/jdk8u/hotspot: 69087d08d473 src/share/vm/oops/markOop.hpp

在 64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:

3.1.2.3.2 klass pointer

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。 如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项 - XX:+UseCompressedOops 开启指针压缩,其中,oop即ordinaryobject pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

  1. 每个Class的属性指针(即静态变量)
  2. 每个对象的属性指针(即对象变量)
  3. 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。对象头 = Mark Word + 类型指针(未开启指针压缩的情况下)在32位系统中,Mark Word = 4 bytes,类型指针 =4bytes,对象头 = 8 bytes = 64 bits;在 64位系统中,Mark Word = 8 bytes,类型指针 = 8bytes,对象头 = 16 bytes = 128bits;

3.1.2.3.3 实例数据

就是类中定义的成员变量。

3.1.2.3.4 对齐填充

对齐填充并不是必然存在的,也没有什么特别的意义,他仅仅起着占位符的作用,由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头正好是8字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

3.1.2.3.5 ObjectMonitor 数据结构
class ObjectMonitor {
    // 对象头信息
    markOop _header;
    // 获取锁的次数
    int _count;
    // 等待队列
    Thread* _waiters;
    // 重入次数
    int _recursions;
    // 所属对象
    oop _object;
    // 当前拥有锁的线程
    Thread* _owner;
    // 等待队列
    WaitSet _WaitSet;
    // 入口队列
    EntryList _EntryList;
 
    // 加锁操作
    void lock();
    // 解锁操作
    void unlock();
    // 尝试加锁操作
    bool try_lock();
    // 等待操作
    void wait(jlong millis, bool interruptable);
    // 通知操作
    void notify(Thread* target_thread);
}

有几个重要的属性

  •  _WaitSet:是一个集合,当线程获到锁之后,但是还没有完成业务逻辑,也还没释放锁,这时候调用了Object类的wait()方法,这时候这个线程就会进入_WaitSet这个集合中等待被唤醒,也就是执行nitify()或者notifyAll()方法唤醒
  • _EntryList:是一个集合,当有多个线程来获取锁,这时候只有一个线程能成功拿到锁,剩下那些没有拿到锁的线程就会进入_EntryList集合中,等待下次抢锁
  •  _Owner:当一个线程获取到锁之后,就会将该值设置成当前线程,释放锁之后,这个值就会重新被设置成null
  •  _count:当一个线程获取到锁之后,_count的值就会+1,释放锁之后就会-1,只有当减到0之后,才算真正的释放掉锁了,其它线程才能来获取这把锁,synchornized可重入锁也是基于这个值来实现的。

hotspot 源码内容如下:

3.1.3 monitor 对象锁原理

synchronized 内部包括ContentionList、EntryList、WaitSet、在OnDeckOwner、!Owner这6个区域,每个区域的数据都代表锁的不同状态。

  • ContentionList:锁竞争队列,所有请求锁的线程都被放在竞争队列中。
  • EntryList:竞争候选列表,在ContentionList中有资格成为候选者来竞争锁资源的线程被移动到了 Entry List 中。
  • WaitSet:等待集合,调用wait方法后被阻塞的线程将被放在WaitSet中。
  • OnDeck:竞争候选者,在同一时刻最多只有一个线程在竞争锁资源,该线程的状态被称为 OnDeck。
  • Owner:竞争到锁资源的线程被称为Owner状态线程。
  • !Owner:在Owner线程释放锁后,会从Owner的状态变成!Owner。
3.1.3.1 执行流程图

3.1.3.2 执行流程说明
  • synchronized 在收到新的锁请求时首先自旋,如果通过自旋也没有获取锁资源,则将被放入锁竞争队列 ontentionList中。
  • 为了防止锁竞争时 ContentionList 尾部的元素被大量的并发线程进行 CAS访问而影响性能,Owner 线程会在释放锁资源时将ContentionList中的部分线程移动到EntryList中,并指定EntryList中的某个线程(一般是最先进入的线程)为OnDeck线程。Owner线程并没有直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,让OnDeck 线程重新竞争锁。在Java中把该行为称为“竞争切换”,该行为牺牲了公平性,但提高了性能。
  • 获取到锁资源的OnDeck线程会变为Owner线程,而未获取到锁资源的线程仍然停留在 EntryList中。
  • Owner线程在被wait方法阻塞后,会被转移到WaitSet队列中,直到某个时刻被notify 方法或者 notifyAll 方法唤醒,会再次进人 EntryList中。ContentionList、EntryList、WaitSet中的线程均为阻塞状态,该阻塞是由操作系统来完成的(在Linux内核下是采用pthread mutexlock内核所数实现的)。

3.1.4 总结

在synchronized中,在线程进人ContentionList 之前,等待的线程会先尝试以自旋的方式获取锁,如果获取不到就进人ContentionList,该做法对于已经进入队列的线程是不公平的,因此synchronized是非公平锁。另外,自旋获取锁的线程也可以直接抢占OnDeck 线程的锁资源。

synchronized是一个重量级操作,需要调用操作系统的相关接口,性能较低,给线程加锁的时间有可能超过获取锁后具体逻辑代码的操作时间。

JDK 1.6对synchronized做了很多优化,引人了适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等以提高锁的效率。锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫作锁膨胀。在JDK1.6中默认开启了偏向锁和轻量级锁,可以通过XX:UseBiasedLocking 禁用偏向锁。

四、Synchronized优化

在JDK1.5的时候,Doug Lee推出了ReentrantLock,lock的性能远高于synchronized,所以JDK团队就在JDK1.6中,对synchronized做了大量的优化。

4.1 锁粗化

4.1.1 定义

如果在一个循环中,频繁的获取和释放做资源,这样带来的消耗很大,锁粗化就是将锁的范围扩大,避免频繁的竞争和获取锁资源带来不必要的消耗。

4.1.2 代码示意

package com.ningzhaosheng.thread.concurrency.features.atom.syn;

/**
 * @author ningzhaosheng
 * @date 2024/3/10 17:34:21
 * @description 测试锁粗化
 */
public class TestSynchronizedExpansion {
    // 正常代码
    public static void increment1() {
        StringBuffer sb = new StringBuffer();
        for(int i = 0;i< 100;i++){
            synchronized (TestSynchronizedExpansion.class){
                sb.append(i+"aa");
            }
        }
        System.out.println(sb.toString());
    }

    /**
     * 锁粗化示意代码
     */
    public static void increment2() {
        StringBuffer sb = new StringBuffer();
        synchronized (TestSynchronizedExpansion.class){
        for(int i = 0;i< 100;i++){
                sb.append(i+"aa");
            }
        }
        System.out.println(sb.toString());
    }

    public static void main(String[] args) {
         increment1();
    }

}

4.2 锁消除

4.2.1 定义

在synchronized修饰的代码中,如果不存在操作临界资源的情况,会触发锁消除,你即便写了synchronized,他也不会触发。

锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。

4.2.2 示意代码

package com.ningzhaosheng.thread.concurrency.features.atom.syn;

/**
 * @author ningzhaosheng
 * @date 2024/3/10 17:46:31
 * @description 测试锁消除
 */
public class TestSynchronizedRemove {
    /**
     * 这个方法并没有存在共享资源,即使加了synchronized,也不会触发同步
     */
    public static synchronized void increment() {
        System.out.println("aaaaaaa");
    }
    public static void main(String[] args) {
        increment();
    }
}

4.3 锁升级(锁膨胀)

4.3.1 定义

锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫作锁膨胀。

4.3.2 锁升级过程

  • 无锁、匿名偏向:当前对象没有作为锁存在。
  • 偏向锁:如果当前锁资源,只有一个线程在频繁的获取和释放,那么这个线程过来,只需要判断,当前指向的线程是否是当前线程 。

      如果是,直接拿着锁资源走。

      如果当前线程不是我,基于CAS的方式,尝试将偏向锁指向当前线程。如果获取不到,触发锁升级,升级为轻量级锁。(偏向锁状态出现了锁竞争的情况)

  • 轻量级锁:会采用自旋锁的方式去频繁的以CAS的形式获取锁资源(采用的是自适应自旋锁

     如果成功获取到,拿着锁资源走
     如果自旋了一定次数,没拿到锁资源,锁升级。

  • 重量级锁:就是最传统的synchronized方式,拿不到锁资源,就挂起当前线程。(涉及用户态和内核态的切换)

好了,本次内容就分享到这,欢迎关注本博主。如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

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

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

相关文章

windows安装ssh

一、下载ssh https://github.com/PowerShell/Win32-OpenSSH/releases/download/v8.1.0.0p1-Beta/OpenSSH-Win64.zip 二、安装ssh 解压到C:\Program Files\OpenSSH-Win64 配置环境变量 把 C:\Program Files\OpenSSH-Win64 加到path环境变量里面 C:\Program Files\OpenSSH-Win64&…

百度文心一言(ERNIE bot)API接入Android应用

百度文心一言&#xff08;ERNIE bot&#xff09;API接入Android应用实践 - 拾一贰叁 - 博客园 (cnblogs.com) Preface: 现在生成式AI越来越强大了&#xff0c;想在android上实现一个对话助手的功能&#xff0c;大概摸索了一下接入百度文心一言API的方法。 与AI助手交换信息的…

elementary OS7 Ubuntu 22.04中硬盘挂载报错

elementary OS7 Ubuntu 22.04中硬盘挂载报错 背景目标思路解决方法 背景 上周末安装elementaryos7的过程中将windows10的引导文件搞丢了&#xff0c;这两天准备修复一下&#xff0c;保险期间将固态硬盘上的文件备份到移动硬盘上&#xff0c;备份过程中出现报错的问题&#xff…

DUKPT流程简析

文章目录 一、DUKPT流程简析 一、DUKPT流程简析 接着上一篇文章DUKPT讲 依旧引用上图&#xff0c;我们单看POS和Acquirer Host这两个结点之间&#xff08;其它结点之间的处理&#xff0c;基本类似&#xff09;&#xff1a; Acquirer在布放POS到商场时&#xff0c;已经提前给…

【C语言进阶篇】文件操作(上)

【C语言进阶篇】文件操作&#xff08;上&#xff09; &#x1f955;个人主页&#xff1a;开敲 &#x1f525;所属专栏&#xff1a;C语言 &#x1f33c;文章目录&#x1f33c; 1. 为什么使用文件&#xff1f; 2. 什么是文件&#xff1f; 2.1 程序文件 2.2 数据文件 2.3 文件名…

dubbo 源码系列之-集群三板斧---负载均衡(二)

在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容&#xff0c;还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。 LeastActiveLoadBala…

模拟实现 atoi 函数

一、函数介绍 原型 int atoi(const char *nptr); 二、使用atoi 三、使用发现 可以发现&#xff1a;会先过滤掉空格&#xff0c;还能识别正负号&#xff0c;当第一次遇到正负号了&#xff0c;后面没接着是数字就返回0&#xff0c; 如果45 5aa 结果是45&#xff0c;说明前面识…

Pytorch CUDA Reflect Padding 算子实现详解

CUDA 简介 CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的一种并行计算平台和应用编程接口&#xff08;API&#xff09;&#xff0c;允许软件开发者和软件工程师使用NVIDIA的图形处理单元&#xff08;GPU&#xff09;进行通用计算。自2007…

2024年C语言最新经典面试题汇总(11-20)

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

Chapter 17 Input Filter Design

Chapter 17 Input Filter Design 在switching converter前面我们总想加一个input filter, 这样可以减少输入电流的谐波EMI(conducted electromagnetic interference). 另外, Input filter可以保护converter和负载不受输入电压瞬态变化的影响, 从而提高了系统稳定性. 如下图所…

BEVFormer v2论文阅读

摘要 本文工作 提出了一种具有透视监督&#xff08;perspective supervision&#xff09;的新型鸟瞰(BEV)检测器&#xff0c;该检测器收敛速度更快&#xff0c;更适合现代图像骨干。现有的最先进的BEV检测器通常与VovNet等特定深度预训练的主干相连&#xff0c;阻碍了蓬勃发展…

C++命名空间和内联函数

目录 命名空间 内联函数 概述 特性&#xff1a; 命名空间 在C/C中&#xff0c;变量&#xff0c;函数和和类这些名称都存在于全局作用域中&#xff0c;可能会导致很多冲突&#xff0c;使用命名空间的目的是对标识符的名称进行本地化&#xff0c;避免命名冲突或名字污染&…

鸿蒙OpenHarmony开发实战:【MiniCanvas】

介绍 基于OpenHarmony的Cavas组件封装了一版极简操作的MiniCanvas&#xff0c;屏蔽了原有Canvas内部复杂的调用流程&#xff0c;支持一个API就可以实现相应的绘制能力&#xff0c;该库还在继续完善中&#xff0c;也欢迎PR。 使用说明 添加MiniCanvas依赖 在项目entry目录执行…

由浅到深认识Java语言(21):Math类

该文章Github地址&#xff1a;https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…

UE像素流公网(Windows、Liunx)部署无需GPU服务器

@TOC 前言 之前有个前端地图服务项目要改成UE来渲染3D,有需要在云服务器上多实例运行,所以就先研究了Windows版本的像素流云渲染,后来客户的云服务器是Linux版CectOS系统,加上又有了一些后端服务在上面运行了不能重装成Windows,所以就又着手去研究了Linux系统的云渲染。…

【动手学深度学习】深入浅出深度学习之PyTorch基础

目录 一、实验目的 二、实验准备 三、实验内容 1. 数据操作 2. 数据预处理 3. 线性代数 4. 微积分 5. 自动微分 四、实验心得 一、实验目的 &#xff08;1&#xff09;正确理解深度学习所需的数学知识&#xff1b; &#xff08;2&#xff09;学习一些关于数据的实用…

SLAM算法与工程实践——CMake使用(4)

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址&#xff1a; SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

第28章 ansible的使用

第28章 ansible的使用 本章主要介绍在 RHEL8 中如何安装 ansible 及 ansible的基本使用。 ◆ ansible 是如何工作的 ◆ 在RHEL8 中安装ansible ◆ 编写 ansible.cfg 和清单文件 ◆ ansible 的基本用法 文章目录 第28章 ansible的使用28.1 安装ansible28.2 编写ansible.cfg和清…

springboot+vue考试管理系统

基于springboot和vue的考试管理系统 001 springboot vue前后端分离项目 本文设计了一个基于Springbootvue的前后端分离的在线考试管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

113 链接集10--ctrl+左键单击多选

1.ctrl左键单击多选&#xff0c;单击单选 精简代码 <div class"model-list"><divmousedown.prevent"handleClick(item, $event)"class"model-list-item"v-for"item in modelList":key"item.id":class"{ model…