Synchronized的锁升级过程是怎样的?

文章目录

  • 一、Synchronized的使用
    • 1、修饰实例方法
    • 2、修饰静态方法
    • 3、修饰代码块
    • 4、总结:
  • 二、Monitor
    • 1、Java对象头
      • 1.1 32 位虚拟机的对象头
      • 1.2 64位虚拟机的对象头
    • 2、Mark Word 结构
    • 3、Moniter
    • 4、Synchronized 字节码
    • 5、轻量级锁
    • 6、锁膨胀
    • 7、自旋优化
    • 8、偏向锁
    • 9、偏向锁的撤销
      • 9.1 hashcode
      • 9.2 其它线程使用对象
      • 9.3 调用 wait/notify
    • 10、批量重偏向、撤销
    • 11、锁消除

一、Synchronized的使用

Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronized 关键字的使用方式主要有下面 3 种

  • 修饰实例方法

  • 修饰静态方法

  • 修饰代码块

1、修饰实例方法

给当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁

synchronized void method() {//业务代码
}


2、修饰静态方法

给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁

这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。

synchronized static void method() {//业务代码
}

静态 synchronized 方法和非静态 synchronized 方法之间的调用不互斥

  • 如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。


3、修饰代码块

对括号里指定的对象/类加锁:

  • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁
synchronized(this) {//业务代码
}


4、总结:

  • synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁;
  • synchronized 关键字加到实例方法上是给对象实例上锁;
  • 尽量不要使用 synchronized(String a) ,因为 JVM 中,字符串常量池具有缓存功能,多个线程使用相同的字符串值,实际使用的是同一个对象



二、Monitor

Java对象由三部分组成

  • 对象头
  • 对象体:对象体里放的是非静态的属性,也包括父类的所有非静态属性(private修饰的也在这里,不区分可见性修饰符),基本类型的属性存放的是具体的值,引用类型及数组类型存放的是引用指针。
  • 对齐填充

1、Java对象头

1.1 32 位虚拟机的对象头

普通对象

Mark Word :存储对象自身的运行时数据,hashCode、gc年龄以及锁信息等

Klass Word :指向Class对象

数组对象

相对于普通对象多了记录数组长度

所以对于一个int类型整数来说,它占用4字节,而一个Integer对象,在32位虚拟机中包含了8字节对象头,4字节数据,一共12字节,加上内存对齐,就是16字节



1.2 64位虚拟机的对象头

  • Markword:存储对象自身运行时数据如hashcode、gc分代年龄及锁信息等,64位系统总共占用8个字节。
  • 类型指针:对象指向类元数据地址的指针,jdk8默认开启指针压缩,64位系统占4个字节
  • 数组长度:若对象不是数组,则没有该部分,不分配空间大小,若是数组,则为4个字节长度



2、Mark Word 结构

32位虚拟机

64位虚拟机

  • 对象的hashCode占31位,重写类的hashCode方法返回int类型,只有在无锁情况下,在有调用的情况下会计算该值并写到对象头中,其他情况该值是空的。
  • 分代年龄占4位,最大值也就是15,在GC中,当survivor区中对象复制一次,年龄加1,默认是到15之后会移动到老年代。
  • 是否偏向锁占1位,无锁和偏向锁的最后两位都是01,使用这一位来标识区分是无锁还是偏向锁。
  • 锁标志位占2位,锁状态标记位,同是否偏向锁标志位标识对象处于什么锁状态。
  • 偏向线程ID占54位,只有偏向锁状态才有,这个ID是操作系统层面的线程唯一id,跟java中的线程id是不一致的


3、Moniter

Moniter称为监视器或者管程,是操作系统提供的对象

每个Java对象都可以关联一个Moniter对象,如果使用synchronized给对象上锁(重量级),该对象的Mark Word中就被设置指向Moniter对象的指针


Moniter结构

  • 刚开始Moniter中Owner为null
  • 当Thread-2执行synchronized(obj)后,就会将Moniter的所有者Owner置位Thread-2,Moniter只能有一个Owner
    • obj对象的MarkWord中最初保存的是对象的hashcode、gc年龄等信息,同时锁标志位为01,表示无锁。当获取锁后,会将这些信息保存在Moniter对象中,然后MarkWord存储的就是指向Moniter的指针,锁标志位为10(重量级锁)
  • 在Thread-2上锁的过程中,如果Thread-1、Thread-3也来执行synchronized(obj),就会进入EntryList,处于BLOCKED状态
  • Thread-2执行完同步代码块的内容后,唤醒EntryList中等待的线程来竞争锁,竞争是非公平的


4、Synchronized 字节码

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {synchronized (lock) {counter++;}
}

对应的字节码为

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: getstatic #2 // <- lock引用 (synchronized开始)3: dup4: astore_1 // lock引用 -> slot 15: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针6: getstatic #3 // <- i9: iconst_1 // 准备常数 110: iadd // +111: putstatic #3 // -> i14: aload_1 // <- lock引用15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList16: goto 2419: astore_2 // e -> slot 2 20: aload_1 // <- lock引用21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList22: aload_2 // <- slot 2 (e)23: athrow // throw e24: returnException table:from to target type6    16  19    any19   22  19    anyLineNumberTable:line 8: 0line 9: 6line 10: 14line 11: 24LocalVariableTable:Start Length Slot Name Signature0     25     0    args [Ljava/lang/String;StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class "[Ljava/lang/String;", class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4

0:拿到lock的引用

4:将lock的引用存储到 slot1 中

5:将lock对象的 MarkWord 置为 Monitor 指针,原本存储的信息就存储到 Monitor 中

6-11:执行counter++操作

14:从 slot1 中获取lock对象的引用

15:将 lock 对象的 MarkWord 重置,原本MarkWord 存储的是hashcode、gc年龄等信息,当 lock 获取锁后,将MarkWord 置位 Monitor 指针。重置就是将这些信息重新写到 MarkWord 中,同时唤醒 EntryList

16:goto 24 执行24行,退出

在Exception table中设置了监控异常的行数,如果6-16行有异常,就去执行19行

19:将异常信息 e 存储到slot2中

20:从 slot1 中获取lock对象的引用

21:将 lock对象 MarkWord 重置, 唤醒 EntryList

22:从 slot2 中获取异常信息

23:打印异常信息

注意

  • 通过异常 try-catch 机制,确保一定会被解锁
  • 方法级别的 synchronized 不会在字节码指令中有所体现


5、轻量级锁

如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 synchronized

如下,method1method2方法都对obj对象加锁

static final Object obj = new Object();public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}
}public static void method2() {synchronized( obj ) {// 同步块 B}
}

1、创建 锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

锁记录有两个属性

  • 锁记录地址,同时用00标记表示轻量级锁
  • Object referenct指向锁的对象

锁的对象obj中有对象头、对象体,对象头中Mark Word存储的是hashcode、gc年龄等信息

2、让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 ObjectMark Word,将 Mark Word 的值存入锁记录,然后将锁记录的信息存到ObjectMark Word

3、如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁

4、如果 cas 失败,有两种情况

  • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  • 如果是自己执行了synchronized锁重入,那么再添加一条 Lock Record 作为重入的计数
    • 此时锁记录中Object reference指向Object,但是由于ObjectMark World位置已经是00轻量级锁状态,因此这条锁记录存储为null

5、当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

6、当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

  • 成功,则解锁成功

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


6、锁膨胀

锁膨胀:轻量级锁升级为重量级锁

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


static Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块}
}

1、当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

2、这时 Thread-1 加轻量级锁失败,进入锁膨胀流程

  • 为 Object 对象申请 Monitor 锁,让 ObjectMARK WORLD指向Moniter锁地址
  • 在堆区创建一个锁记录【Lock Record】对象,该对象包含了持有该锁的线程信息,然后ObjectMARK WORLD也会记录这个对象的地址
  • 然后 Thread-1进入 MonitorEntryList,处于BLOCKED状态

3、当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,会失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程


7、自旋优化

重量级锁竞争的时候,还可以使用自旋(循环尝试获取重量级锁)来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。 (进入阻塞再恢复,会发生上下文切换,比较耗费性能)


自旋重试成功的情况

自旋重试失败的情况

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

8、偏向锁

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

  • Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

这里的线程id是操作系统赋予的id 和 Thread的id是不同的

static final Object obj = new Object();
public static void m1() {synchronized( obj ) {// 同步块 Am2();}
}
public static void m2() {synchronized( obj ) {// 同步块 Bm3();}
}
public static void m3() {synchronized( obj ) {// 同步块 C}
}

没有开启偏向锁,会使用轻量级锁 ,每次重入都会执行CAS操作 开启偏向锁,每次锁重入仅判断当前ThreadID是否是自己


对象头格式

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0,不保存hashcode信息
  • 偏向锁默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

测试

利用 jol 第三方工具来查看对象头信息

public static void main(String[] args) throws IOException {Dog d = new Dog();ClassLayout classLayout = ClassLayout.parseInstance(d);new Thread(() -> {log.debug("synchronized 前");System.out.println(classLayout.toPrintableSimple(true));synchronized (d) {log.debug("synchronized 中");System.out.println(classLayout.toPrintableSimple(true));}log.debug("synchronized 后");System.out.println(classLayout.toPrintableSimple(true));}, "t1").start();
}
11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中,也就是偏(心)向某个线程了


禁用偏向锁

禁用偏向锁后,创建对象后,最后3位是001,无锁状态。加锁后,变为000,轻量级锁,同时保存了锁记录地址。释放锁后,变回001无锁状态,同时清除锁记录地址

11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
11:13:10.021 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

9、偏向锁的撤销

9.1 hashcode

Dog d = new Dog(); 后加上一句 d.hashCode();

  • 正常状态对象一开始是没有 hashCode 的,第一次调用才生成
  • 调用了 hashCode() 后会撤销该对象的偏向锁
11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015 
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001

因为调用了hashcode(),但是默认是偏向锁,存储的是线程id,没有内存去存储hashcode,因此会撤销偏向锁,用来存储hashcode

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

9.2 其它线程使用对象

当有其它线程使用偏向锁对象时【没有发生锁竞争】,会将偏向锁升级为轻量级锁

private static void test2() throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (TestBiased.class) {TestBiased.class.notify();}}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (TestBiased.class) {try {TestBiased.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}, "t2");t2.start();
}


9.3 调用 wait/notify

重量级锁才支持 wait/notify,调用后,锁直接升级为重量级锁

public static void main(String[] args) throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));try {d.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t1");t1.start();new Thread(() -> {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (d) {log.debug("notify");d.notify();}}, "t2").start();
}
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

总结

  • 默认情况下,偏向锁是开启的,即这个锁归这个对象所拥有

  • 如果有其他线程获取锁或者调用hashcode,那么升级为轻量级锁

  • 如果发生锁竞争或者调用wait/notify,那么升级为重量级锁


10、批量重偏向、撤销

  • 对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID 。当(某类型对象)撤销偏向锁超过阈值 20 次后,jvm 会在给(所有这种类型的状态为偏向锁的)对象加锁时重新偏向至新的加锁线程
  • 当撤销偏向锁阈值超过 40 次后,jvm 会将整个类的所有对象都会变为不可偏向的,新建的该类型对象也是不可偏向的
    • 例如:当前有40个锁对象,刚开始都偏向t1线程。现在t2线程获取这40个锁对象,1-19个锁对象会撤销偏向锁,第20个锁对象往后,会撤销t1的偏向锁,将偏向锁设置为t2【达到20阈值】。然后t3线程获取这40个锁对象,由于前19个锁对象已经是非偏向锁了,从第20个开始,又会撤销偏向锁,最后撤销次数达到40阈值后,会将所有的锁变为不可偏向的,即使新创建的对象也是不可偏向的。

演示批量重偏向

private static void test3() throws InterruptedException {Vector<Dog> list = new Vector<>();Thread t1 = new Thread(() -> {for (int i = 0; i < 30; i++) {Dog d = new Dog();list.add(d);synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}synchronized (list) {list.notify();}}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (list) {try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("===============> ");for (int i = 0; i < 30; i++) {Dog d = list.get(i);log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t2");t2.start();
}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
...
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - ===============> 
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101  // 原始轻量级锁偏向t1
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000  // t2获取锁,将锁升级为轻量级锁
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001  // 释放锁后,轻量级锁被撤销
...
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 // 第20次,初始轻量级锁偏向t1
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 // 第20次撤销锁,达到阈值,jvm将后边所有锁偏向t2
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
...
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

11、锁消除

锁消除 :JIT即时编译器会对字节码做进一步优化,下边代码中o是一个局部变量,不会共享,所以编译后,不会执行加锁操作,而是直接执行x++

public class MyBenchmark {static int x = 0;public void b() throws Exception {//这里的o是局部变量,不会被共享,JIT做热点代码优化时会做锁消除Object o = new Object();synchronized (o) {x++;}}
}

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

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

相关文章

C++ 代码实现局域网即时通信功能 (windows 系统 客户端)

本项目使用C实现具备多个客户端和服务器端即时通信聊天功能软件 一&#xff1a;项目内容 使用C实现一个具备多客户端和一个服务器端即时通信功能的聊天软件。 本项目的目的是 学习在windows平台下&#xff0c;进行C网络开发的基本概念&#xff1a;TCP/IP socket通信&#xff0…

Java集合之HashMap的数据结构分析

总所周知&#xff0c;Java中键值对集合&#xff0c;我们最常用的就是HashMap&#xff0c;那么它的数据结构&#xff0c;以及如何存储键值对&#xff0c;包括为什么使用红黑树&#xff0c;链表等许多数据结构&#xff0c;下面我们一起学习交流 1.HashMap的数据结构&#xff1a;…

scratch二次开发:如何修改toolbox宽度

大家好&#xff0c;我是小黄。 使用场景&#xff1a;有时候我们开发图形化编程时&#xff0c;我们的积木块很长&#xff0c;导致一部分无法显示&#xff0c;我们想要把目录区域位置放大&#xff0c;比如下面红色方框区域位置&#xff0c;那么改如何实现这个过程呢&#xff1f;…

Qt,获取其他.exe文件的标准输出流的信息(printf/print的输出信息)

比如&#xff0c;通过Python编写爬虫软件功能是运行程序获取豆瓣电影排行榜信息&#xff0c;并通过print打印出来。将其打包成.exe,通过Qt来调用&#xff0c;并获取到.exe程序运行的结果 简单示例代码&#xff1a; // 创建 QProcess 对象QProcess process;// 连接信号槽以获取…

嵌入式学习Day14---C语言进阶

目录 一、构造类型 1.1.结构体 1.存储 2.输入输出&#xff08;传参&#xff09; 3.结构体数组 1.2.共同体&#xff08;联合体&#xff09; 1.格式 2.存储 3.测试一个平台是打端还是小端 1.3.枚举 1.格式 2.特点 二、位运算&#xff08;操作二进制&#xff09; 2.1.&a…

培训第十六天(web服务apache与nginx)

上午 静态资源 根据开发者保存在项目资源目录中的路径访问静态资源html 图片 js css 音乐 视频 f12&#xff0c;开发者工具&#xff0c;网络 1、web基本概念 web服务器&#xff08;web server&#xff09;&#xff1a;也称HTTP服务器&#xff08;HTTP server&#xff09;&am…

翻译: 可视化深度学习神经网络一

这是一个随意书写的28*28像素、分辨率很低的数字 3 但你的大脑一看见就能轻松辨识出来 &#xff0c;我想要你好好欣赏这点 人脑能够毫无障碍地辨识是非常厉害的 我的意思是&#xff0c;这个、这个、还有这个&#xff0c;都能被识别为 3 即使前后图像的图形组成有很大差异 当你…

懂个锤子Vue 项目工程化扩展:

Vue项目工程化扩展&#x1f4f6;&#xff1a; 前言&#xff1a;当然既然学习框架的了&#xff0c;HTMLCSSJS三件套必须的就不说了&#xff1a; JavaScript 快速入门 紧跟前文&#xff0c;目标学习Vue2.0——3.0&#xff1a; 懂个锤子Vue、WebPack5.0、WebPack高级进阶 涉及的…

WEB前端开发中如何实现大文件上传?

大文件上传是个非常普遍的场景&#xff0c;在面试中也会经常被问到&#xff0c;大文件上传的实现思路和流程。在日常开发中&#xff0c;无论是云存储、视频分享平台还是企业级应用&#xff0c;大文件上传都是用户与服务器之间交互的重要环节。随着现代网络应用的日益复杂化&…

康师傅JAVA核心内容

链接&#xff1a;康师傅JAVA核心内容 (qq.com)

黑龙江等保测评如何做到既全面又高效?

在黑龙江省进行等保测评&#xff0c;必须在全面和高效之间寻求一个平衡点&#xff0c;以保证网络的安全性和可靠性。黑龙江等保测评怎样才能在二者之间发现黄金交汇点&#xff1f;下面&#xff0c;我们来揭开谜底。 精准定位&#xff0c;明确测评范围 首先&#xff0c;一个综…

Docker与LXC差异以及相关命令

容器&#xff1a;Docker与LXC差异以及相关命令 ​ LXC与Docker对比&#xff0c;LXC只实现了进程沙盒化&#xff0c;不支持在不同的机器上进行移植&#xff1b;Docker将应用的所有配置和环境进行了抽象&#xff0c;打包到一个容器中&#xff0c;此容器可以在任何安装了docker的…

vscode搭建rust开发环境

由于rustrover不是免费的&#xff0c;此处教学搭建一套基于vscode的rust开发环境&#xff0c;可运行&#xff0c;可调式 1.下载vscode1.91.1 Download Visual Studio Code - Mac, Linux, Windows 2.下载插件 打开网站下载插件 rust-analyzer-0.4.2049、vscode-lldb-1.10.0、…

IDEA项目的依赖(pom.xml文件)导入问题及解决

前言&#xff1a;该文章为转载&#xff0c;没有仔细的看 IDEA新建项目和pom.xml文件被修改时&#xff0c;右下角都会出现 Maven projects need to be imported&#xff08;项目需要导入依赖&#xff09; 如下&#xff0c;点击 Import Changes导入后&#xff0c;有时会一直处于…

NAS、SAN 与 DAS 的比较与应用场景

文章目录 1. NAS&#xff08;网络附加存储&#xff09;定义特点实现成本&#xff1a;适用场景 2. SAN&#xff08;存储区域网络&#xff09;定义特点实现成本&#xff1a;适用场景 3. DAS&#xff08;直接附加存储&#xff09;定义特点实现成本&#xff1a;适用场景 区别总结结…

Redis学习[1] ——基本概念和数据类型

Redis学习[1] ——基本概念和数据类型 一、Redis基础概念 1.1 Redis是什么&#xff0c;有什么特点&#xff1f; Redis是一个基于**内存的数据库&#xff0c;因此读写速度非常快**&#xff0c;常用作缓存、消息队列、分布式锁和键值存储数据库。支持多种数据结构&#xff1a;…

Java 内推 | 教育行业缺口来了,研发,运维,产品,教研,职能,营销... 别错过

Java 内推 | 教育行业缺口来了&#xff0c;研发&#xff0c;运维&#xff0c;产品&#xff0c;教研,职能&#xff0c;营销… 别错过 岗位职责&#xff1a; 1、根据公司战略及业务规划&#xff0c;参与部门业务架构分析与设计&#xff0c;包含规划立足当前、面向未来的应用架构…

源码编译安装,及nginx服务控制、监控块

1.源码编译安装&#xff1a; [root17dns ~]# wget https://nginx.org/download/nginx-1.27.0.tar.gz 2.解压&#xff1a; [root17dns ~]# tar -zxvf nginx-1.27.0.tar.gz 3.安装gcc等工具 [root17dns ~]# yum -y install gcc gcc-c [root17dns ~]# yum -y install make lrzsz …

postman给全部接口添加请求头数据(如token)

如果给没有一个接口添加请求头token就太慢了&#xff0c;如下图。可以点击所有接口的所属的目录。点击“Scripts”&#xff0c;点击Pre-request按钮。加入代码&#xff1a; pm.request.addHeader("Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI111pXVCJ9.eyJjbGFpbXMiOnsiaW…