并发编程(2)—Java 对象内存布局及 synchornized 偏向锁、轻量级锁、重量级锁介绍

news2024/11/24 17:31:06

一、Java 对象内存布局

1、对象内存布局

一个对象在 Java 底层布局(右半部分是数组连续的地址空间),如下图示:
在这里插入图片描述

总共有三部分总成:

1. 对象头:储对象的元数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁等等。
2. 实例数据:存储对象实际的数据内容,即程序员定义的各种类型的变量。
3. 对其填充:为了 JVM 能够更快地访问对象内部的数据,会在实例数据后面填充额外的空间,使得对象的大小能够被虚拟机的内存管理系统所整除(一般都是8的倍数)。

具体对象头的大小和实例数据的大小,与 Java 虚拟机的具体实现、对象的类型、虚拟机运行时参数等都有关系,一般不是固定的数值。需要注意的是,数组对象与普通对象的内存布局是不一样的,数组对象会额外存储数组长度信息。

1.1、对象头

点击查看 hotspot 官网文档

(1) mark word 标记字

对象头两部分组成:

在这里插入图片描述
在这里插入图片描述

1. 对象标记(mark word):储对象的元数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁
2. 类元信息(klass pointer):存储的是指向该对象类元数据(klass)所在的首地址。

在 64位操作系统上,Markword 占了8个字节,类型指针占了8个字节,一共占16个字节。也就是说你随便 new 一个对象对象头就直接占了 16个字节(但是不一定,可能会压缩类型指针)。

在这里插入图片描述

(2) klass pointer 类型指针

可以参考下图,类型指针指向方法区,比如有个 Customer 类,new 一个 Customer 实例,这个实例的类型指针指向方法区中的 Customer 类元信息。

在这里插入图片描述

1.2、实例数据

存放类的 Field 数据信息,包括父类的属性信息;如果是数组实例部分,还需要包括数组的长度,这部分内存按照4个字节对齐。

举个例子如下:

public class MarkwordDemo {

    public static void main(String[] args) {
        new Apple();
    }
}
class Apple {
}

直接 new 一个空属性 Apple 实例,在内存中就已经占用16字节(不考虑类型指针压缩),如果 Apple 类中还有其他属性呢?如下所示:

public class MarkwordDemo {

    public static void main(String[] args) {
        new Apple();
    }
}
class Apple {
    int size = 100;
    char a = 'a';
}

一个 int 类型占 4个字节,一个 char 字符占1个字节,所以 new 一个 Apple 实例就会占用 16+5 = 21 个字节,但是最终会占用24个字节,因为 Java 底层为了方便内存管理,需要将其对齐填充,并且一般是8的倍数,所以是24字节。

1.3、对其填充

虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按照8字节补充对齐。

二、同步锁底层探究

markOop.hpp 源码中有如下一段注释,如下图示:

在这里插入图片描述

把上述注释简化后,得到64位虚拟机对象头示意图,如下:

在这里插入图片描述

知道对象内部基本结构,那么下面来看看之前的 synchornized 同步锁在对象头中是怎么的变化过程。

1、 Java 查看对象内存布局

可以借助 Java 工具类 jol,帮助查看 new Object() 在内存中的布局创,如下所示:

1、先引入依赖

依赖包推荐使用 0.9 版本的,其他版本可能有不一样的效果,珍重。

		<dependency>
			<groupId>org.openjdk.jol</groupId>
			<artifactId>jol-core</artifactId>
			<version>0.9</version>
		</dependency>

2、演示代码

class MyObject {
}

public class ObjectMarkWordDemo {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
    }
}

直接 new class MyObject(),然后通过 ClassLayout 工具类查看内存布局,输出结果如下:

在这里插入图片描述

在这里插入图片描述

在 MyObject 类添加两个类型的变量,如下所示:

class MyObject {
	int i = 25;
	boolean flag = false;
}

public class ObjectMarkWordDemo {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new MyObject()).toPrintable());
    }
}

然后输出之后的 Java 内存布局如下图示:

在这里插入图片描述

从上面可以看到,类型指针按理应该是占8个字节的,但现在是占用4个字节,我们可以通过命令查询 JVM 启动运行了哪些命令:

java -XX:+PrintCommandLineFlags -version

在这里插入图片描述

从上面 + 号就可以看出 JVM 默认采取类型指针压缩,可以节约内存空间,现在去修改一下这个参数设置,如下图示:

-XX:-UseCompressedClassPointers

在这里插入图片描述

开启之后,在重新测试下,输出结果如下:

在这里插入图片描述

学习上面已经知道怎么查看 Java 内存布局,现在再来学习一下,synchornized 锁优化 、锁升级相关。

2、synchornized 锁研究

在来看看对象头中 mardkword 标记字内存结构,如下图示:

在这里插入图片描述

synchornized 锁优化背景:

用锁能够实现安全性,但是也会带来性能的下降。无锁能够基于线程并提升程序性能,但是会带来安全性下降,那么怎么才能做到平衡呢?

所以在 jdk1.5开始就采取 synchornized 锁升级来提高程序性能,并且做到程序安全性。

在这里插入图片描述

在 jdk1.5 之前都是 synchornized 都是使用的操作系统重量级锁,每次上锁都需要进行用户态内核态之间的切换,切换的时候又伴随很多数据拷贝过程,性能很低。

在这里插入图片描述

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

在 Java 早期版本,synchornized 属于重量级锁,效率低下,因为监视器(Monitor)是依赖底层操作系统的 Mutex Lock 实现的,挂起和恢复线程都需转入内核态完成,阻塞或者唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态切换需要消耗 CPU 时间,如果通过代码块中内容过于简单,这种切换成本太高。

比如我们在代码块中加上 synchornized 关键字,代码如下:

class MyObject {
    int a = 25;
    char b = 'b';
}
public class ObjectMarkWordDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();

        new Thread(()->{
            synchronized (myObject) {
                System.out.println(">>>>>>");
            }
        }).start();
        
    }
}

在 Java 层面加上一个 synchronized 关键字,底层默认会加上一个看不见的锁—Monitor 锁,如下图示:

在这里插入图片描述

那么 Monitor 是如何与 Java 对象以及线程进行关联?

  1. 如果一个 Java 对象被某个线程锁住,该对象中的 markword 字段中的 lock word 会指向 Monitor 的起始地址。
  2. Monitor 的 Owner 字段会存放拥有相关联对象锁的线程 ID

3、锁优化过程

(1) 无锁

看下面这段代码没有加锁,如下所示:

public class ObjectMarkWordDemo {
    public static void main(String[] args) throws InterruptedException {
        Object abc = new Object();
        System.out.println(ClassLayout.parseInstance(abc).toPrintable());
    }
}

如果无锁,正常一个对象在 Java 内存中对象中的 markword 标记字,如下图示:

在这里插入图片描述

通过 Java 打印出信息如下:

在这里插入图片描述

注意上述展示的结果倒过来看,蓝色框框的001 表示此时无锁状态,在无锁状态时红色框框的31位表示 hashCode,其中一位是忽略补0。但是发现 hashCode 没有发现展现出来,是因为这个操作是懒加载,需要调方法才会触发 hashCode。例如下面代码:

public class ObjectMarkWordDemo {
    public static void main(String[] args) {
		MyObject myObject = new MyObject();
        System.out.println("十进制表示: myObject.hashCode() = " + myObject.hashCode());
        System.out.println("二进制表示:"+Integer.toBinaryString(myObject.hashCode()));
        System.out.println("十六进制表示:"+Integer.toHexString(myObject.hashCode()));
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}

输出结果如下:

十进制表示: myObject.hashCode() = 1435804085
二进制表示:1010101100101001010000110110101
十六进制表示:5594a1b5

在这里插入图片描述

为了方便观察,把 hashCode 编码各个进制位打印出来。从右边往左开始拷贝(从右边往左边开始8个字节8个字节拷贝出来组成一个长串,前25位属于 unused 暂时不管它,后面31位属于 hashCode(蓝色框框的),红色框框3位表示锁相关,001 表示无锁状态)。第一拷贝:1010101(前面的0是补位忽略,不要拷贝,就是前面25位中 unused 的其中1位而已),第二个拷贝:10010100,第三个拷贝:10100001,第四个拷贝:10110101 然后连成一串就和上面打印出来的二级制(1010101100101001010000110110101)一模一样,这31位bit位就是存放的 hashCode。

从上面也可以看出,此时无锁状态时就是用 001 表示的。

(2) 偏向锁

当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁,如下图示(红色框):

在这里插入图片描述

只要哪个线程获取到偏向锁,就会把当前线程指针保存到这个对象的前54位中(无锁保存 hashCode 码),并且偏向锁位置成1,

为什么需要2个 bit 位表示锁标志位?
具体来说,synchronized 锁最开始是无锁状态,当第一个线程来竞争锁时,会将对象头中的锁标志位修改为偏向锁,然后将线程 ID 记录在对象头中,表示该线程获得了偏向锁。当第二个线程来竞争锁时,如果发现对象头中记录的线程 ID 和当前线程 ID 一致,那么它就可以获得锁,否则就需要撤销偏向锁,并转为轻量级锁状态。当有多个线程竞争锁时,就会进入到重量级锁状态。因此,为了实现锁升级过程,Java 在对象头中增加了两个 bit 位来表示锁标志位,以实现从无锁状态到偏向锁状态、再到轻量级锁状态、最后到重量级锁状态的转换过程。
这里有四种情况,所以刚好使用2bit表示上面出现的四种情况 。

假如有个线程执行到 synchornized 代码块时,JVM 使用 CAS 操作把线程指针 ID 记录到 mark word 中,并修改偏向锁位,标示当前线程获得到该锁。锁对象变成偏向锁(通过 CAS 修改对象头中的锁标志位)。执行完同步代码块之后,线程并不会主动释放偏向锁。

线程获得了锁,可以执行同步代码块。当线程2到达同步代码块时会判断此时持有锁的线程是否是自己,如果是自己的线程 ID,那说明还持有这个对象的锁,就可以继续执行同步代码块。由于之前没有主动去释放偏向锁,这里也就不需要重新加锁(不用重新去调用操作系统的 Mutex 上锁)。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎在这里没有额外开销,性能极高。

查询偏向锁是否开启

java -XX:+PrintFlagsInitial | grep BiasedLock

运行结果:

intx BiasedLockingBulkRebiasThreshold          = 20                                  {product}
intx BiasedLockingBulkRevokeThreshold          = 40                                  {product}
intx BiasedLockingDecayTime                    = 25000                               {product}
// 然后偏向锁开启之后默认会有4s钟的延迟,测试的时候需要注意,可以将这个值设置成0,方便查看效果
intx BiasedLockingStartupDelay                 = 4000                                {product}
bool TraceBiasedLocking                        = false                               {product}
// JVM 默认开启了偏向锁的设置
bool UseBiasedLocking                          = true                                {product}

开启偏向锁设置

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

通过展示结果 UseBiasedLocking = true 可以知道 JVM 默认是开启偏向锁,但是并不是程序启动就立即开启偏向锁,而是需要延迟 4s 后才会真正开启偏向锁。

为什么要延迟4s开启偏向锁?
由于偏向锁的获取需要一定的时间开销,因此JVM并不是在对象创建时立即开启偏向锁。相反,JVM在对象创建后,等待一定的时间(默认为4秒),以观察该对象的使用情况。如果在这段时间内,只有一个线程访问了该对象,那么JVM就会将对象的锁标志位设置为偏向锁,并将线程ID记录在对象头中,表示该线程已经获取了该对象的锁。如果在这段时间内,有多个线程访问了该对象,那么JVM就不会将对象的锁标志位设置为偏向锁,而是直接将锁标志位设置为轻量级锁或重量级锁,使用常规的加锁方式。

这种等待一定时间才开启偏向锁的策略,是为了避免在短时间内频繁创建和销毁对象,导致偏向锁的开销大于加锁的性能损耗。

演示效果是,需要将偏向锁延迟设置成0s,如图图示:

在这里插入图片描述
VM 中的命令如下:

-XX:BiasedLockingStartupDelay=0

演示代码如下:

public class ObjectMarkWordDemo {
    public static void main(String[] args) {

        Object abc = new Object();
        new Thread(() -> {
            synchronized (abc) {
                // 注意这里不要写任何代码操作
                System.out.println(ClassLayout.parseInstance(abc).toPrintable());
            }
        }).start();
    }
}

注意:在上述输出语句上不要写其他代码

输出结果如下:

在这里插入图片描述

可以对内布局中已经变为偏向锁 101。但是现在只是不存在锁竞争的情况下,如果一旦发现竞争就会进行锁撤销,去释放锁变成轻量级锁

(3) 偏向锁撤销

在这里插入图片描述

问题:偏向锁撤销带来性能严重下降?

偏向锁撤销是指在偏向锁状态下,由于竞争或者其他原因,需要将对象的锁状态恢复到无锁状态的过程。偏向锁撤销是指撤销偏向锁,恢复到无锁状态。

在偏向锁状态下,如果有其他线程尝试获取锁,则需要先撤销偏向锁。撤销偏向锁的过程需要检查对象的 hashCode 是否发生改变,如果 hashCode 发生改变,则需要撤销偏向锁,否则可以直接将锁升级为轻量级锁。在撤销偏向锁的过程中,需要重新偏向、清除偏向锁标志、设置线程 ID 为 0 等。

偏向锁撤销的过程是比较耗费性能的,因此需要尽量避免偏向锁撤销的情况发生,尤其是在高并发的场景下。

优化: 在竞争激励情况下,可以关闭偏向锁,直接升级到轻量级锁。

偏向锁撤销案例如下:

public class ObjectMarkWordDemo {
    public static void main(String[] args) throws InterruptedException {

        Object abc = new Object();

        synchronized (abc) {
            System.out.println("偏向锁:" + ClassLayout.parseInstance(abc).toPrintable());
        }
        System.out.println("偏向锁:" + ClassLayout.parseInstance(abc).toPrintable());
        new Thread(() -> {
            synchronized (abc) {	
            	System.out.println(">>>>>>发生竞争锁,触发偏向锁撤销...");
            }
        }).start();
        System.out.println("偏向锁撤销:" + ClassLayout.parseInstance(abc).toPrintable());
    }
}

在这里插入图片描述

(4) Lock Record

问题:什么是 Lock Record ?

在这里插入图片描述

线程A在运行期间会在栈帧里面创建一个空间,叫做 Lock Record 记录,用来存储锁记录。当虚拟机检测到这个对象是无锁状态时,就会在这个线程的栈帧上面创建一个这个空间,存储来自 Mark Word 锁相关信息。

Lock Record 里面的数据都是拷贝 Mark Word 里面的,因为锁的相关信息都是在 Mark Word 上面。同时,这个拷贝过程官方名为:Displaced Mark Word。最终通过 CAS 自旋操作,把这个栈帧的指针写到 Mark Word 中,写成功,表示这个线程A获取锁成功。写失败,表示这个锁被其他线程占用。

(5) 轻量级锁

轻量级锁为了在线程近乎交替执行同步代码时提高效率。

主要目的,在没有多线程竞争的前提下,通过 CAS 减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋在阻塞。升级时机,当关闭偏向锁功能或者多线程竞争偏向锁会导致升级为轻量级锁。

假如线程A已经拿到锁,这时候线程B又过来抢夺该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已经是偏向锁;而线程B在争夺发现 Makr Word 中线程ID不是自己的,那么线程B就会进入 CAS 自旋操作希望能够获取到锁。此时线程B操作中有两种情况:

①如果获取锁成功,直接替换 Mark Word 中的线程ID为线程B自己的ID,重新偏向于线程B,该锁保持偏向锁状态,A线程结束,B线程上位。

在这里插入图片描述

②如果获取锁失败,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码块,而在竞争的线程B会进入自旋等待该轻量级锁。

在这里插入图片描述

轻量级锁自旋次数过多,造成CPU资源浪费,在 JDK6 之前默认自旋10次或者自旋线程数量超过 cpu 核数一半直接放弃自旋,升级为重量级锁 10

修改自旋次数命令:

-XX:PreBlockSpin=10

轻量级锁案例如下(可以把偏向锁延迟时间恢复):

-XX:BiasedLockingStartupDelay=4000
package com.xxl.job.admin.mytest;


import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

public class ObjectMarkWordDemo {

    public void test() {
        Object obj = new Object();
        synchronized (obj) {
            System.out.println("111");
        }
        synchronized (obj) {
            System.out.println("111");
        }
        synchronized (obj) {
            System.out.println("111");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 打印 JVM 相关的信息
        // System.out.println(VM.current().details());
        // 打印每个对象是否为 8 的整数倍大小
        // System.out.println(VM.current().objectAlignment());
        MyObject myObject = new MyObject();
        System.out.println(Integer.toHexString(myObject.hashCode()));
        new Thread(()->{
            // 在 myObject 对象头上进行加锁(默认直接干到轻量级锁,这里我非要把他干到偏向锁状态)
            // 默认是开启偏向锁的,所以这里我们只需要把开启偏向锁的延迟时间修改成 0 方便看效果 -XX:+BiasedLockingStartupDelay=0
            synchronized (myObject) {
                // 给这个线程加锁,并且还设置了偏向线程 ID
                System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
            }
        }).start();

        TimeUnit.MICROSECONDS.sleep(500);

        // 锁被释放了,所以这里打印的肯定是无锁状态 001
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());

    }
}
class MyObject {

}

运行结果:

76fb509a
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.xxl.job.admin.mytest.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 29 c3 0a (11101000 00101001 11000011 00001010) (180562408)
      4     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
      8     4        (object header)                           44 c1 00 f8 (01000100 11000001 00000000 11111000) (-134168252)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.xxl.job.admin.mytest.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 29 c3 0a (11101000 00101001 11000011 00001010) (180562408)
      4     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
      8     4        (object header)                           44 c1 00 f8 (01000100 11000001 00000000 11111000) (-134168252)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

(6) 重量级锁

当在轻量级锁一直自旋时,就需要考虑是不是用重量级锁还可以提高性能。

public class ObjectMarkWordDemo {
    public static void main(String[] args) throws InterruptedException {

        Object abc = new Object();
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(() -> {
                synchronized (abc) {
                    System.out.println("重量级锁:" + ClassLayout.parseInstance(abc).toPrintable());
                }
            });
            thread.join();
            thread.start();
        }
    }
}

在这里插入图片描述

(7) 锁粗化

类似下面这个例子:

public class ObjectMarkWordDemo {
    Object abc = new Object();

    public  void show() {
        synchronized (abc) {
            // 复杂操作
        }
        synchronized (abc) {
            // 复杂操作
        }
        synchronized (abc) {
            // 复杂操作
        }
    }
}

使用的一直都是同一把锁,并且前后执行时间都非常短,JVM 会进行优化,将这些锁直接合并成为一个大的锁,可以称之为锁膨胀,提供程序性能。

(8) 锁消除

类似下面这个例子:

public class ObjectMarkWordDemo {

    public void show() {
        Object abc = new Object();
        synchronized (abc) {
            // 复杂操作
        }
    }
 }

show() 方法每次都会加锁,但是这个锁根本没有任何意义,所以 JVM 底层会把它优化掉提高程序性能。

三、常用命令

设置 JVM 堆大小

-Xms10m -Xmx10m

查询 JVM 启动运行了哪些命令

java -XX:+PrintCommandLineFlags -version

关闭对象头中类指针的压缩配置

-XX:-UseCompressedClassPointers

查询偏向锁是否开启

java -XX:+PrintFlagsInitial | grep BiasedLock

运行结果:

intx BiasedLockingBulkRebiasThreshold          = 20                                  {product}
intx BiasedLockingBulkRevokeThreshold          = 40                                  {product}
intx BiasedLockingDecayTime                    = 25000                               {product}
// 然后偏向锁开启之后默认会有4s钟的延迟,测试的时候需要注意,可以将这个值设置成0,方便查看效果
intx BiasedLockingStartupDelay                 = 4000                                {product}
bool TraceBiasedLocking                        = false                               {product}
// JVM 默认开启了偏向锁的设置
bool UseBiasedLocking                          = true                                {product}

开启偏向锁设置

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

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

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

相关文章

Android中的OpenGL

前面有关 Android 音视频的渲染都是使用MediaCodec进行渲染&#xff0c;MediaCodec也有自己的弊端比如无法进行视频的编辑处理&#xff0c;而视频可以 OpenGL ES来进行渲染&#xff0c;可以很好进行处理&#xff0c;比如添加滤镜等&#xff0c;这里介绍下 Android 中 OpenGL&am…

GrowingIO是什么?如何将GrowingIO数据导入其他系统

GrowingIO是什么&#xff1f;GrowingIO 是一站式数据增长引擎整体方案服务商&#xff0c;以数据智能分析为核心&#xff0c;通过构建客户数据平台&#xff0c;打造增长营销闭环&#xff0c;帮助企业提升数据驱动能力&#xff0c;赋能商业决策、实现业务增长。GrowingIO 专注于零…

MyBatis-Plus框架解析?

简单介绍&#xff1a;MyBatis-Plus&#xff08;简称 MP&#xff09;&#xff08;由苞米豆公司开源&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。MP会内置集成部分SQL方法&#xff0c;可以直接…

【应用管理总结 Objective-C语言】

一、把应用管理这个案例,给大家总结一下: 1.今天,经过一天的努力,我们终于把这个九宫格应用管理案例的所有功能都实现了吧, 我们一起来,一边看效果,一边来总结, 2.大家先想一下,当我们实现这个效果,按照最终的那个版本来想一下,这个代码是什么样的一个思路, 1)…

QT打包的两种方式

QT打包的两种方式&#xff1a; 一个是QT5自带的windeployqt&#xff08;不需要下载安装&#xff09;&#xff0c;它可以找到程序&#xff08;exe&#xff09;用到的所有库文件&#xff0c;并且都拷贝到exe程序的当前文件。此时打包的exe较小&#xff0c;需要和拷贝进来的文件放…

大话数据结构-图的深度优先遍历和广度优先遍历

4 图的遍历 图的遍历分为深度优先遍历和广度优先遍历两种。 4.1 深度优先遍历 深度优先遍历&#xff08;Depth First Search&#xff09;&#xff0c;也称为深度优先搜索&#xff0c;简称DFS&#xff0c;深度优先遍历&#xff0c;是指从某一个顶点开始&#xff0c;按照一定的规…

抗锯齿和走样(笔记)

Artifacts&#xff08;瑕疵&#xff09;&#xff1a; 比如人眼采样频率跟不上陀螺的旋转速度&#xff0c;这时就有可能看到陀螺在反方向旋转怎么做抗锯齿&#xff08;滤波&#xff09;&#xff1a; 在采样之前先进行一个模糊操作&#xff0c;可以降低锯齿的明显程度 通过傅里叶…

七【SpringMVC参数绑定】

目录&#x1f6a9;一 . 视图传参到控制器&#x1f6a9;二 . SpringMVC跳转方式&#x1f6a9;三 SpringMVC处理json请求和响应&#x1f6a9;四 SpringMVC静态资源处理✅作者简介&#xff1a;Java-小白后端开发者 &#x1f96d;公认外号&#xff1a;球场上的黑曼巴 &#x1f34e;…

Flask自定义接口,实现mock应用

问题&#xff1a;后端接口已提供&#xff0c;前端需要依赖后端接口返回的数据进行前端页面的开发&#xff0c;如何配合前端&#xff1f; mock接口 flask自定义接口实现查询接口&#xff1a;查询全部、部分查询 具体看下面的代码&#xff1a; #导入包 from flask import Fla…

企业如何选择固定资产管理系统?

如何促进企业内部信息化的建设&#xff0c;实现企业的高效管理和运转&#xff0c;是企业管理员经常考虑的问题。尤其是企业资金占比较多的固定资产该如何高效管理&#xff0c;是大家经常你讨论的问题。我们都知道行政部门管理着百上千件物品&#xff0c;且还要定期进行盘点&…

【python】标准库详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录Standard Library简介python内置对象如何安装发布第三方模块10最好用的模块汇总包的本质datetime模块案例Math模块random模块OS模块sys模块time模块总结自定义模块标准库模块用help查看time模块常用第三方库…

30 openEuler使用LVM管理硬盘-简介和安装

文章目录30 openEuler使用LVM管理硬盘-简介和安装30.1 LVM简介30.1.1 基本概念30.2 安装30 openEuler使用LVM管理硬盘-简介和安装 30.1 LVM简介 LVM是逻辑卷管理&#xff08;Logical Volume Manager&#xff09;的简称&#xff0c;它是Linux环境下对磁盘分区进行管理的一种机…

【苹果内购支付】关于uniapp拉起苹果内购支付注意事项、实现步骤以及踩过的坑

前言 Hello&#xff01;又是很长时间没有写博客了&#xff0c;因为最近又开始从事新项目&#xff0c;也是第一次接触关于uniapp开发原生IOS应用的项目&#xff0c;在这里做一些关于我在项目中使用苹果内购支付所实现的方式以及要注意的事项&#xff0c;希望能给正在做uniapp开…

Hive 数据倾斜

数据倾斜&#xff0c;即单个节点任务所处理的数据量远大于同类型任务所处理的数据量&#xff0c;导致该节点成为整个作业的瓶颈&#xff0c;这是分布式系统不可能避免的问题。从本质来说&#xff0c;导致数据倾斜有两种原因&#xff0c;一是任务读取大文件&#xff0c;二是任务…

Centos7 服务器基线检查处理汇总

1、服务器超时设置 问题描叙 TMOUT的值大于key2且小于等于{key2}且小于等于key2且小于等于{key1}视为合规 查看命令&#xff1a;export检测结果 超时时间:0处理方式 备份/etc/profile文件 cp /etc/profile /etc/profile_bak编辑profile文件 vim /etc/profile修改/新增 TMO…

Spring Cloud(微服务)学习篇(三)

Spring Cloud(微服务)学习篇(三) 1 nacos中使用openFeign(调用方式)实现短信发送 1.1 在shop-sms-api中创建com.zlz.shop.sms.api.service/vo/dto/util,目录结构如下所示 1.2 在pom.xml(shop-sms-api)中加入如下依赖 <dependencies><dependency><groupId>…

西电算法分析与设计核心考点汇总(期末真题,教材算法导论)

文章目录前言一、历年考题1.1 判断题1.2 单选题1.3 复杂度计算1.4 分治1.5 算法设计&#xff08;01背包&#xff0c;最短路径&#xff09;1.6 最大子数组问题1.7 算法设计&#xff08;最长回文串&#xff09;二、核心考点2.1 概述部分考点2.1.1 循环不变式loop-invariants2.1.2…

绪论 基本概念

数据结构 第一章 绪论 概念 数据data&#xff1a;是对客观事物的符号表示。在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。 数据元素(data element:是数据的基本单位&#xff0c;在计算机程序中通常作为一个整体进行考虑和处理。 数据对象(data …

软件测试面试题和简历模板(面试前准备篇)

一、问题预测 1、让简单介绍下自己&#xff08;这个不用说了每次面试开场&#xff09; 面试官&#xff0c;你好&#xff0c;我叫xxx&#xff0c;xx年本科毕业&#xff0c;从事软件测试将近3年的时间。在此期间做过一些项目也积累过一些经验&#xff0c;能够独立地完成软件测试…

经典的卷积神经网络(VGG,GoogLeNet等)

LeNet LeNet原文地址&#xff1a;https://ieeexplore.ieee.org/abstract/document/726791 Lenet是一个 7 层的神经网络&#xff08;不包含输入层&#xff09;&#xff0c;包含 3 个卷积层&#xff0c;2 个池化层&#xff0c;2 个全连接层。它的网络结构图如下所示&#xff1a…