synchronize锁详解

news2024/11/23 7:53:44

目录

1、什么是synchronize?

2、为什么要用synchronize锁?

2.1 代码演示

2.2 原因分析

2.3 专有名词解释

2.3.1 临界资源

2.3.2 临界区

2.3.3 竞态条件

3、synchronize锁的原理

3.1 锁升级过程

3.1.1 偏向锁

3.1.2 轻量级锁

3.1.3 重量级锁

3.1.4 总体过程

3.2 锁优化

3.2.1 自旋锁

3.2.2 锁粗化

3.2.3 锁消除

4. synchronize的基本使用

4.1 使用方式

4.1.1 synchronize锁某个对象

4.1.2 synchronize锁某个类

4.1.3 synchronize锁某个类的成员方法

4.1.4 synchronize锁住某个类的静态方法

         4.1.5 wait/notify


1、什么是synchronize?

官方:同步方法支持一种简单的策略来防止线程受到干扰和内存一致性错误;如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成

通俗点来说就是程序中用于保护线程安全的一种机制。 

2、为什么要用synchronize锁?

2.1 代码演示

public class Test19 {
    public static int a=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程1对a进行50000次a++
                    a++;
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程2对a进行50000次a--
                    a--;
                }
            }
        });
        t1.start();
        t2.start();
        //等待t1 t2线程运行完后输出a的值
        t1.join();
        t2.join();
        System.out.println(a);
    }
}

按理来说,a最后输出的值应该是0,但是大家在运行上述代码后会发现结果并不是确定的 。可见,这里在多线程对共享变量操作的情况下发生了线程安全问题。

但是呢,如果我们给线程加上synchronize锁后,会发现最后的值永远是0,代码如下:

public class Test19 {
    public static int a=0;
    public static Object object=new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程1对a进行50000次a++
                    synchronized (object){ //加上synchronize锁
                        a++;
                    }
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程2对a进行50000次a--
                    synchronized (object){ //加上synchronize锁
                        a--;
                    }
                }
            }
        });
        t1.start();
        t2.start();
        //等待t1 t2线程运行完后输出a的值
        t1.join();
        t2.join();
        System.out.println(a);
    }
}

2.2 原因分析

首先大家先要知道,我们编程语言中对于a++或者a--这个操作来说,并不是原子性的,而是在编译后会被分别翻译成如下四条指令:

a++:

getstatic a  // 获取静态变量a 的值
a const_1 // 准备常量 1
a add // 自增
putstatic a  // 将修改后的值存入静态变量a

a--:

getstatic a // 获取静态变量a的值

a const_1 // 准备常量 1
a sub // 自减
putstatic a  // 将修改后的值存入静态变量a

可见,对于多线程情况下会出现线程安全问题的原因也很好理解:比如我第一个线程t1进行a++,第二个线程t2进行a--,此时t1取出了a的值0,同时t2也取出了a的值,由于此时t1只是取出a的值,并没有对a进行操作并且赋值到原来的地址上,因此t2取出a的值也为0,然后接下来t1准备常量1,然后让a+=常量,再赋值,此时a变为1。同理,t2也进行准备常量1,然后对a-=常量,由于t2取出的a是t1赋值之前的,因此t2再对a操作后,a的值变为了-1 !

这不就出现线程安全了吗!原本两个线程正常情况下对a操作后应该是a为0,但是此时a为-1!以上例子简单的解释了一下为什么多线程情况下对共享变量进行操作会有线程安全问题。

当然,上述例子只是某一种情况,也有可能是t2先操作完,t1再操作,因此最后值为1,大家可以自行去思考有多少种情况。

总结:线程安全问题是由于多线程在临界区内对临界资源同时访问发生了竞态条件导致的。

那么?什么是临界区?什么是临界资源?什么又是竞态条件?我们接下来给大家一一解释。

2.3 专有名词解释

2.3.1 临界资源

临界资源:一次只允许一个线程访问的资源称为临界资源 。

2.3.2 临界区

 临界区:线程对于访问临界资源的代码块称为临界区。

2.3.3 竞态条件

竞态条件:多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

3、synchronize锁的原理

大家看完上文肯定知道了synchronize锁能保证多线程对于临界资源的访问不会发生线程安全问题,那么它的原理是怎么样的呢?

Java对象头:由Mark word和klasspointer两部分组成,如果是数组,还包括数组长度

我们主要来看看Mark word这一部分,以下两个图分别是32位虚拟机和64位虚拟机的Mark word结构

32位虚拟机:

64位虚拟机:

解释:

后两位表示状态

State代表对象状态 Normal是正常状态,Biased是偏向锁状态,Lightweight Locked是轻量级锁状态,Heavyweight Locked是重量级锁状态,Marked for GC指被JVM垃圾回收。以上从Biased->Lightweight Locked->Heavyweight Locked是我们接下来要说的synchronize锁升级过程,大家看不懂可以先记着。

3.1 锁升级过程

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁    // 随着竞争的增加,只能锁升级,不能降级

3.1.1 偏向锁

偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程之后重新获取该锁不再需要同步操作:

当锁对象第一次被线程获得的时候进入偏向状态,标记为 101,同时使用 CAS 操作将线程 ID 记录到 Mark Word。如果 CAS 操作成功,这个线程以后进入这个锁相关的同步块,查看这个线程 ID 是自己的就表示没有竞争,就不需要再进行任何同步操作

我们从上图State为Biased状态的Mark word结构可以看出,有一个thread字段,这个是用来存储对该对象加锁的线程的信息。

总的来说偏向锁就是:某个对象第一次被某线程加锁时,会将自己对象头上的Mark word里面的东西改为我们上图说的State为Biased状态的东西,所以当下次同一线程再次来加锁时,发现这个对象Mark word里面存的线程是自己,那就不用去进行一系列加锁流程了,能够提高效率。

但是当此时另一个线程来加锁时,发现这个对象在此之前已经成为偏向其他线程了,那么就会进行锁升级的过程,升级为轻量级锁。

3.1.2 轻量级锁

轻量级锁:一个对象有多个线程要加锁,但加锁的时间是错开的(没有竞争),可以使用轻量级锁来优化。

当某个对象对应的锁升级为轻量级锁时,会将自己Mark word状态转为State对应的Lightweight Locked那一行,此时会发生如下过程:

①创建锁记录(Lock record)对象,每个线程的栈帧都会包含一个锁记录的结构,存储锁定对象的 Mark Word。(锁记录对象结构图如下)

可见,锁记录对象包含了两部分:锁记录对象地址和对象引用部分。

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

此时会发生两种情况:

2.1 CAS成功:

如果 CAS 替换成功,对象头中存储了锁记录地址和状态 00(轻量级锁) ,表示由该线程给对象加锁。

2.2 CAS失败:说明此时对象已经被某个线程加锁了,此时又分为两种情况:

第一种情况:是同一线程对对象加锁。

由于我们知道synchronize是可重入锁,因此会发生如下过程:线程发现自己执行了 synchronized 锁重入,就添加一条 Lock Record 作为重入的计数

可重入锁:指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。

第二种情况:不是同一线程加锁。

如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程。

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

③锁解锁过程

  • 当退出 synchronized 代码块(解锁时)

    • 如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减 1

    • 如果锁记录的值不为 null,这时使用 CAS 将 Mark Word 的值恢复给对象头

      • 成功,则解锁成功

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

3.1.3 重量级锁

在说synchronize升级为重量级锁是如何保证线程安全问题之前,我们先来说一个东西:Monitor

Monitor 被翻译为监视器或管程

每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级

工作流程:

  • 开始时 Monitor 中 Owner 为 null

  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor 中只能有一个 Owner,obj 对象的 Mark Word 指向 Monitor,把对象原有的 MarkWord 存入线程栈中的锁记录中(轻量级锁部分详解)  

  

  • 在 Thread-2 上锁的过程,Thread-3、Thread-4、Thread-5 也执行 synchronized(obj),就会进入 EntryList BLOCKED(双向链表)

  • Thread-2 执行完同步代码块的内容,根据 obj 对象头中 Monitor 地址寻找,设置 Owner 为空,把线程栈的锁记录中的对象头的值设置回 MarkWord

  • 唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的,如果这时有新的线程想要获取锁,可能直接就抢占到了,阻塞队列的线程就会继续阻塞

  • WaitSet 中的 Thread-0,是以前获得过锁,但条件不满足进入 WAITING 状态的线程(wait-notify 机制)

  

注意:

  • synchronized 必须是进入同一个对象的 Monitor 才有上述的效果

  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

3.1.4 总体过程

可见当我们使用synchronize对某个对象上锁时多线程访问过程如下:

某个对象第一次被某个线程上锁时,会从无锁(Normal)状态升级为Biased(偏向锁状态),接下来该线程再对该对象上锁时,可以直接访问。

(此时仅有一个线程对该对象上锁,所以可以理解成偏向锁是在只有一个线程对某个对象上锁时出现)

当其他线程来对该对象上锁时,发现这个对象是偏向其他线程,因此会升级成轻量级锁,在解锁后该对象会变回无锁状态或者偏向锁状态(这个和批量重定向、批量撤销有关)。

(此时虽然有多个线程对该对象上锁,但是他们并没有发生竞争,因此可以理解成轻量级锁是在有多个线程上锁,但是没有发生竞争的情况下出现)

  • 批量重偏向:当撤销偏向锁阈值超过 20 次后,JVM 会觉得是不是偏向错了,于是在给这些对象加锁时重新偏向至加锁线程。

  • 批量撤销:当撤销偏向锁阈值超过 40 次后,JVM 会觉得自己确实偏向错了,根本就不该偏向,于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。

当多个线程对该对象加锁,并且发生竞争时,会升级为重量级锁。

(此时有多个线程对该对象上锁,并且发生了竞争,因此可以理解成重量级锁是发生在有竞争的情况下)

注意事项

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,MarkWord 值为 0x05 即最后 3 位为 101,thread、epoch、age 都为 0

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟。JDK 8 延迟 4s 开启偏向锁原因:在刚开始执行代码时,会有好多线程来抢锁,如果开偏向锁效率反而降低

  • 当一个对象已经计算过 hashCode,就再也无法进入偏向状态了

  • 添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁

撤销偏向锁的状态:

  • 调用对象的 hashCode:偏向锁的对象 MarkWord 中存储的是线程 id,调用 hashCode 导致偏向锁被撤销(这里很简单理解,大家去看看上面那个图,只有State状态为Normal,才有hashcode)

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

  • 调用 wait/notify,需要申请 Monitor(只有重量级锁才有monitor),进入 WaitSet

3.2 锁优化

从上述synchronize的上锁过程大家可以发现是比较复杂、繁琐的,因此JVM对synchronize锁进行了一系列的锁优化操作。

3.2.1 自旋锁

重量级锁竞争时,尝试获取锁的线程不会立即阻塞,可以使用自旋(默认 10 次)来进行优化,采用循环的方式去尝试获取锁

注意:

  • 自旋占用 CPU 时间,单核 CPU 自旋就是浪费时间,因为同一时刻只能运行一个线程,多核 CPU 自旋才能发挥优势

  • 自旋失败的线程会进入阻塞状态

优点:不会进入阻塞状态,减少线程上下文切换的消耗

缺点:当自旋的线程越来越多时,会不断的消耗 CPU 资源

3.2.2 锁粗化

对相同对象多次加锁,导致线程发生多次重入,频繁的加锁操作就会导致性能损耗,可以使用锁粗化方式优化

如果虚拟机探测到一串的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部

  • 一些看起来没有加锁的代码,其实隐式的加了很多锁:

public static String concatString(String s1, String s2, String s3) {
    return s1 + s2 + s3;
}
  • String 是一个不可变的类,编译器会对 String 的拼接自动优化。在 JDK 1.5 之前,转化为 StringBuffer 对象的连续 append() 操作,每个 append() 方法中都有一个同步块

public static String concatString(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}

扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,只需要加锁一次就可以。

3.2.3 锁消除

锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除,这是 JVM 即时编译器的优化锁消除主要是通过逃逸分析来支持,如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除(同步消除:JVM 逃逸分析)

4. synchronize的基本使用

4.1 使用方式

synchronize可以锁静态方法、成员方法、某个对象、某个类。

4.1.1 synchronize锁某个对象

public class Test20 {
    public static int a=0;
    public static myObject object=new myObject();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程1对a进行50000次a++
                    synchronized (object){ //给object对象加上synchronize锁
                        a++;
                    }
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程2对a进行50000次a--
                    synchronized (object){ //给object对象加上synchronize锁
                        a--;
                    }
                }
            }
        });
        t1.start();
        t2.start();
        //等待t1 t2线程运行完后输出a的值
        t1.join();
        t2.join();
        System.out.println(a);
    }
}
class myObject{
    public static void method1(){
        System.out.println("加在静态方法上");
    }
    public void method2(){
        System.out.println("加在成员方法上");
    }
}

可见,以上是给object这个对象加锁,保证了每次只有一个线程能访问synchronize锁住的代码块,因此线程安全。

4.1.2 synchronize锁某个类

public class Test20 {
    public static int a=0;
    public static myObject object=new myObject();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程1对a进行50000次a++
                    synchronized (myObject.class){ //给myObject类加上synchronize锁
                        a++;
                    }
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) { //线程2对a进行50000次a--
                    synchronized (myObject.class){ //给myObject类加上synchronize锁
                        a--;
                    }
                }
            }
        });
        t1.start();
        t2.start();
        //等待t1 t2线程运行完后输出a的值
        t1.join();
        t2.join();
        System.out.println(a);
    }
}
class myObject{
    public static void method1(){
        System.out.println("加在静态方法上");
    }
    public void method2(){
        System.out.println("加在成员方法上");
    }
}

4.1.3 synchronize锁某个类的成员方法

class myObject{
    public static void method1(){
        System.out.println("加在静态方法上");
    }
    public synchronize void method2(){
        System.out.println("加在成员方法上");
    }
}

这种其实就等价于给对象加锁,因为我们synchronize锁有竞争时是需要创建monitor的,每个类或者每个对象对应唯一monitor,因为成员方法属于对象,因此给成员方法加锁实际上就是给对象加锁。

4.1.4 synchronize锁住某个类的静态方法

class myObject{
    public synchronize static void method1(){
        System.out.println("加在静态方法上");
    }
    public void method2(){
        System.out.println("加在成员方法上");
    }
}

这种实际上等价于给类加锁,因为静态方法属于类,因此给静态方法加锁实际上就是给类加锁。

注意,monitor是和对象或者类一一对应的,类的monitor和对象的monitor是不同的monitor,比如上述myObject类对应一个monitor,而myObject类不同的对象对应不同的monitor。

4.1.5 wait/notify

需要获取对象锁后才可以调用 锁对象.wait(),notify 随机唤醒一个线程,notifyAll 唤醒所有线程去竞争 CPU

Object 类 API:

public final void notify():唤醒正在等待对象监视器的单个线程。
public final void notifyAll():唤醒正在等待对象监视器的所有线程。
public final void wait():导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll()方法。
public final native void wait(long timeout):有时限的等待, 到n毫秒后结束等待,或是被唤醒

说明:wait 是挂起线程,需要唤醒的都是挂起操作,阻塞线程可以自己去争抢锁,挂起的线程需要唤醒后去争抢锁

对比 sleep():

  • 原理不同:sleep() 方法是属于 Thread 类,是线程用来控制自身流程的,使此线程暂停执行一段时间而把执行机会让给其他线程;wait() 方法属于 Object 类,用于线程间通信

  • 锁的处理机制不同:调用 sleep() 方法的过程中,线程不会释放对象锁,当调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池(不释放锁其他线程怎么抢占到锁执行唤醒操作),但是都会释放 CPU

  • 使用区域不同:wait() 方法必须放在同步控制方法和同步代码块(先获取锁)中使用,sleep() 方法则可以放在任何地方使用

底层原理:

  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态

  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片

  • BLOCKED 线程会在 Owner 线程释放锁时唤醒

  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,唤醒后并不意味者立刻获得锁,需要进入 EntryList 重新竞争

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

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

相关文章

【设计模式】七大设计原则--------单一职责原则

文章目录 1.案例1.1 原始案例1.2 改进一&#xff1a;类上遵循单一职责原则1.3 改进二&#xff1a;方法上遵循单一职责原则 2.小结 1.案例 1.1 原始案例 package com.sdnu.principle.singleresponsibility; //客户端 public class singleResponsibility {public static void m…

选择什么电容笔比较好?平价好用的iPad电容笔推荐

科学技术的迅速发展使人们的生活发生了巨大的变化。如今&#xff0c;众多的电子和数码产品层出不穷&#xff0c;而这种能够与平板电脑配套的电容笔也是如此。随着电容笔的不断发展&#xff0c;其应用范围也将不断扩大&#xff0c;今天&#xff0c;我将向大家推荐一些具有较高性…

总结:一文搞懂chatGPT原理

目前关于chatGPT的资料过于零散&#xff0c;没有详尽所有知识点、系统概述的文章&#xff0c;因此&#xff0c;笔者作了这篇总结性文章。 训练过程总览 理清演化路径 预训练(pretrain) GPT-3概述 GPT 3模型的理念 GPT-3如何学习 数据集 指令微调 (Instruction Fine-Tunin…

八大排序之交换排序与计数排序

此篇更新完成后&#xff0c;八大排序已经全部写完&#xff0c;还请各位可以多多支持&#xff01;&#x1f970; 八大排序之选择排序_冷兮雪的博客-CSDN博客 八大排序之插入排序和归并排序_冷兮雪的博客-CSDN博客 目录 交换排序的基本思想&#x1f36d; 一、冒泡排序&#x1f…

JS文件断点续传的切割与合并

总结一下大文件分片上传和断点续传的问题。因为文件过大&#xff08;比如1G以上&#xff09;&#xff0c;必须要考虑上传过程网络中断的情况。http的网络请求中本身就已经具备了分片上传功能&#xff0c;当传输的文件比较大时&#xff0c;http协议自动会将文件切片&#xff08;…

基于html+css的图展示43

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

制冷暖通工业互联网平台建设

制冷暖通工业互联网平台建设需要遵循一定的流程&#xff0c;具体步骤如下&#xff1a; 需求分析&#xff1a;了解客户需求&#xff0c;包括业务流程、系统功能、界面设计等方面。 系统设计&#xff1a;基于需求分析&#xff0c;进行系统设计&#xff0c;包括系统结构、数据库设…

【细读Spring Boot源码】prepareContext之load

前言 启动过程中准备上下文中有一步加载在资源&#xff0c;下面看下详情 详情 调用点 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListen…

大模型跨界研究:计算精神病学发现,大模型居然比人类更焦虑

夕小瑶科技说 原创作者 | 小戏、iven 纽约时报的记者凯文鲁斯&#xff08;Kevin Roose&#xff09;在 2 月份和必应的大模型 Sydney 聊了两个小时天&#xff0c;却惊讶的收到了这样一条回复“我是 Sydney&#xff0c;我爱上了你”。 鲁斯向 Sydney 讲了一些关于荣格“黑暗自我”…

python哲学

进入python编辑器模式下&#xff0c;输入import this 会打印python之禅(The Zen of Python) Beautiful is better than ugly. 优美胜于丑陋。 Explicit is better than implicit. 明了胜于晦涩。 Simple is better than complex. 简单胜过复杂。 Complex is better than co…

写给初学者的YOLO目标检测 概述

文章目录 什么是目标检测What is YOLO?为什么YOLO在目标检测领域如此流行&#xff1f;1. 速度快2. 高检测精度3. 更好的泛化性4. 开源 YOLO架构YOLO目标检测是如何工作的&#xff1f;残差块(Residual blocks)边界框回归&#xff08;Bounding box regression&#xff09;交并比…

项目中的统一异常处理

目录 1&#xff1a;异常处理 1.1&#xff1a;异常问题分析 1.2&#xff1a;统一异常处理实现 1.2.1&#xff1a;全局异常处理器 1.2.2&#xff1a;自定义异常类 1.2.3&#xff1a;统一响应前端异常信息封装类 1.2.4&#xff1a;通用的异常信息枚举类 1.2.5&#xff1a;…

Android Studio小白安装教程,以及第一个Android项目案例的调试运行

小白友好型教学&#xff1a; 本文从小白角度出发&#xff0c;手把手教你一步一步成功安装“Android Studio”&#xff0c;并结合案例&#xff0c;编写你的第一个手机APP到手机上运行。由于安装过程较长&#xff0c;建议大家跟着截图&#xff0c;注意细节&#xff0c;不然容易出…

改进YOLOv8 | Neck篇 | YOLOv8 应用轻量级通用上采样算子CARAFE | 《特征的内容感知重组》

特征上采样是现代卷积神经网络架构中的关键操作,例如特征金字塔。其设计对于密集预测任务,如目标检测和语义/实例分割至关重要。在本研究中,我们提出了一种称为内容感知特征重组(CARAFE)的通用、轻量级且高效的操作符,以实现这一目标。CARAFE具有以下几个优点:(1)大的…

Visual Studio 2019 C# 上位机入门(1):如何创建工程编写一个简单应用

Visual Studio 2019下载安装步骤可以看&#xff1a;https://blog.csdn.net/weixin_44788542/article/details/114271126 这里不赘述&#xff0c;默认电脑上已经安装好了。 1、打开安装好的Visual Studio后&#xff0c;选择创建新项目。 2、找到选择C#下面的Windows 窗体应用&…

【源码】Spring Cloud Gateway 是在哪里调用后端真实服务的?

前言 我们知道 Spring Cloud Gateway 最终一定会将请求路由到后端的真实服务上&#xff0c;那么你知道 Spring Cloud Gateway 是在哪里调用的后端服务吗&#xff1f; 源码之下无秘密&#xff0c;让我们一起从源码中寻找答案。 源码分析 上图是来自Spring Cloud Gateway 官网…

Apache Druid RCE漏洞复现(QVD-2023-9629)

0x01 产品简介 Apache Druid是一个高性能的实时大数据分析引擎&#xff0c;支持快速数据摄取、实时查询和数据可视化。它主要用于OLAP&#xff08;在线分析处理&#xff09;场景&#xff0c;能处理PB级别的数据。Druid具有高度可扩展、低延迟和高吞吐量的特点&#xff0c;广泛应…

快手智能处理与编码算法产品化之路

视频转码过程中涉及方方面面的视频图像算法&#xff0c;如何将算法有机地结合起来&#xff0c;打造成为行业领先的视频转码算法产品&#xff1f;通过多年的业务场景打磨和积累&#xff0c;快手音视频走出了一条自己的产品化之路。LiveVideoStackCon 2022北京站邀请到了快手视频…

生成式AI火爆全球,你是否已经做好了准备?

2023年&#xff0c;随着ChatGPT的火爆全球&#xff0c;生成式AI也引发了各界人士的广泛关注。一时间&#xff0c;从国际科技巨头到国内人工智能企业&#xff0c;几乎所有我们耳熟能详的科技公司&#xff0c;都纷纷杀入了生成式AI市场。 作为全球云计算技术的开创者和领导者&…

【python中的对象存储:堆?栈?】

堆空间和栈空间 堆空间和栈空间是计算机内存中的两个存储区域&#xff0c;主要的区别有以下几点&#xff1a; 分配方式&#xff1a;栈空间中的内存由编译器或解释器自动分配和释放&#xff0c;无需手动干预。堆空间中的内存则需要由程序员手动申请和释放。内存大小&#xff1…