【并发编程】2-Synchronized

基本概念

  1. 线程安全问题三个要素:多线程、共享资源、非原子性操作;
  2. 产生的根本原因:多条线程同时对一个共享资源进行非原子性操作;
  3. Synchronized解决线程安全问题的方式:通过互斥锁将多线程的并行执行变为单线程串行执行,同一时刻只让一条线程执行,也就是当多条线程同时执行一段被互斥锁保护的代码(临界资源)时,需要先获取锁,这时只会有一个线程获取到锁资源成功执行,其他线程将陷入等待的状态,直到当前线程执行完毕释放锁资源之后,其他线程才能执行;
  4. Synchronized可以保证可见性和有序性,但无法禁止指令重排序

Synchronized锁粒度及应用方式

Synchronized锁的三种粒度

  1. 锁粒度synchronized本质上是通过对象来加锁的,根据不同的对象类型可分为不同的锁粒度;
    1. this:当前实例锁;
    2. object:对象实例锁;
    3. class:对象锁;

应用方式

  1. 修饰实例成员方法:使用的是this锁类型,这个this代表的是当前new出来的对象;

    synchronized void method() {//业务代码
    }
    
  2. 修饰静态成员方法:使用的是this锁类型,但由于静态成员属于类对象,所以这个this代表的是class对象;

    synchronized static void method() {//业务代码
    }
    
  3. 修饰代码块:修饰代码块时,可以指定锁对象,可以将任意class类对象做为锁资源;

    synchronized(SyncIncrDemo.class) {//业务代码
    }
    
    1. 使用String对象加锁:因为修饰代码块时,可以将任意class类对象做为锁资源,而JAVA中字符串是以Sting对象存储使用的,并且在JVM里有个字符串常量池,用于存储字符串,那么相同值的字符串变量的引用地址可以是同一个,基于这个特性我们可以将synchronized锁粒度变细;

      如:将每一个订单的ID转化为字符串然后进行加锁,这样就能降低锁粒度提高系统并发,但在加锁时需要考虑加锁的String对象是不是同一个,需要考虑String对象使用的是堆中对象还是字符串常量池中的对象,若是堆中对象需要使用.intern()将堆中对象刷入字符串常量池中;

      /*** @author xrl* @date 2024/6/25 22:47*/
      public class Demo1 {public static void main(String[] args) {new Thread(() ->t1(111L), "AAA").start();new Thread(() ->t2(111L), "BBB").start();new Thread(() -> t2(111L), "CCC").start();}public static void t1(Long orderId){// 不能使用  String lock = orderId + "; 原因是orderId是入参,而方法可能在多个地方调用// 所以编译器不会使用常量折叠技术对其进行优化,编译后的代码会被转化为new StringBuilder().append(orderId).append("").toString()String lock = new String(orderId + "").intern();synchronized (lock){System.out.println(Thread.currentThread().getName() + "拿到锁");try {System.out.println("t1业务执行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "释放锁");}}public static void t2(Long orderId){String lock = new String(orderId + "").intern();synchronized (lock){System.out.println(Thread.currentThread().getName() + "拿到锁");try {System.out.println("t2业务执行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "释放锁");}}
      }
      

实现原理

  1. Synchronized是基于Monitor(管程)对象实现的
    1. 获取锁:进入管程对象(显式型:monitorenter指令);
    2. 释放锁:退出管程对象(显示型:monitorexit指令);
  2. synchronized修饰方法使用时是隐式同步的,是通过调用指令,读取运行时常量池中的方法ACC_SYNCHRONIZED标识实现的,也就无法通过javap反编译看到进入/退出管程对象的指令;

JAVA对象的内存布局

  1. 分为三个区域对象头、实例数据、对齐填充

    在这里插入图片描述

    1. 对象头:存储MarkWord和类型指针(ClassMetadataAddress/KlassWord);如果为数组对象,还会存数组长度(ArrayLength

      • 主要包含unused未使用的空间HashCodeage分代年龄biased_lock是否偏向锁lock锁标记位ThreadID持有偏向锁的线程IDepoch偏向锁时间戳ptr_to_lock_record指向线程本地栈中lock_record的指针ptr_to_heavyweight_monitor指向堆中monitor对象的指针

      • 64位系统中MarkWord结构

        在这里插入图片描述

    2. 实例数据:存放当前对象的属性成员信息,以及父类属性成员信息;

    3. 对齐填充:虚拟机要求对象起始地址必须是8byte的整数倍,避免减少堆内存的碎片空间,并且方便操作系统读取;

monitor对象

  1. 概念

    1. monitor本质是一个特殊的对象,存在于堆中,并且是线程私有的
    2. 每个java对象都存在一个monitor对象与之关联,当一个monitor被某个线程持有后,便会处于锁定状态;由ObjectMonitor实现,具体代码位于HotSpot源码的ObjectMonitor.hpp文件中;
  2. 与线程之间的关联:每个线程都有一个可用的monitor record列表,同时也存在一个全局的可用列表,每一个锁住的对象,都会和monitor关联(对象头的MarkWord中的ptr_to_heavyweight_monitor,指向monitor的起始地址),同时monitor中有一个Owner字段,存放拥有该锁的线程唯一标识,表示该锁被这个线程占用;

  3. 内部结构

    在这里插入图片描述

    1. Contention List:竞争队列,**存放所有请求锁的线程(**后续1.8版本中的_cxq);
    2. Entry List:一个双向链表,存放Contention List有资格成为候选资源的线程
    3. Wait Set:哈希表,调用Object.wait()方法后,被阻塞的线程被放置在这里
    4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被称为OnDeck
    5. Owner拥有这个monitor record线程的唯一标识,为NULL说明没有被占用;
    6. !Owner:当前释放锁的线程;
    7. RcThis:表示blocked阻塞或waiting等待在该monitor record上的线程个数;
    8. Nest:用来实现重入锁的计数。
    9. Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程,唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪,然后因为竞争锁失败又被阻塞),从而导致性能严重下降。Candidate只有两种可能的值,0表示没有需要唤醒的线程;1表示要唤醒一个继任线程来竞争锁;
    10. HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
    11. EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程;
  4. 使用流程

    1. 每个获取锁或等待锁的线程都会被封装成ObjectWaiter对象;
    2. Monitor有两个队列_WaitSet_EntryList,用来保存ObjectWaiter对象列表;
    3. 当多个线程同时访问一段同步代码时,获取到锁的对象会进入到_owner区域,并将owner变量设置为当前线程的唯一标识,同时nest计数器加1,没有获取到锁的对象会加入_EntryList队列中等待;
    4. 若线程调用Object.wait()方法,会释放当前持有的monitorowenr变量回复为null,同时nest计数器减1,并将线程放入到waitSet集合中等待被唤醒;
    5. 当调用Monitor对象的notify()notifyAll() 方法来唤醒 WaitSet 中的等待线程时,会将等待线程移动到 EntryList 队列中等待获取锁的机会;

修饰代码块的原理

  1. 反编译代码

    1. 源代码

      public class SyncDemo{int i;public void incr(){synchronized(this){i++;}}
      }
      
    2. 字节码javac SyncDemo.java javap -p -v -c SyncDemo.class

      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 388 bytesMD5 checksum 0c87d23a96c5f67c7d97908dbd338f95Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#18         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#19         // SyncDemo.i:I#3 = Class              #20            // SyncDemo#4 = Class              #21            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               StackMapTable#13 = Class              #20            // SyncDemo#14 = Class              #21            // java/lang/Object#15 = Class              #22            // java/lang/Throwable#16 = Utf8               SourceFile#17 = Utf8               SyncDemo.java#18 = NameAndType        #7:#8          // "<init>":()V#19 = NameAndType        #5:#6          // i:I#20 = Utf8               SyncDemo#21 = Utf8               java/lang/Object#22 = Utf8               java/lang/Throwable
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0/*-------synchronized修饰incr()中代码块,反汇编之后得到的字节码文件--------*/public void incr();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=3, args_size=10: aload_01: dup2: astore_13: monitorenter // monitorenter进入同步4: aload_05: dup6: getfield      #2                  // Field i:I9: iconst_110: iadd11: putfield      #2                  // Field i:I14: aload_115: monitorexit // monitorexit退出同步16: goto          2419: astore_220: aload_121: monitorexit // monitorexit退出同步22: aload_223: athrow24: returnException table:from    to  target type4    16    19   any19    22    19   anyLineNumberTable:line 5: 0line 6: 4line 7: 14line 8: 24StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class SyncDemo, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4
      }
  2. 从字节码中可知,synchronized修饰代码块,是基于进入管程monitorenter和退出管程monitorexit指令实现的,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置;
    有两条monitorexit指令的原因是解决方法异常结束时,锁释放问题;

修饰方法的原理

  1. 反编译代码

    1. 源代码

      public class SyncDemo {int i;public synchronized void incr() {i++;}
      }
      
    2. 字节码

      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 388 bytesMD5 checksum 0c87d23a96c5f67c7d97908dbd338f95Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#18         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#19         // SyncDemo.i:I#3 = Class              #20            // SyncDemo#4 = Class              #21            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               StackMapTable#13 = Class              #20            // SyncDemo#14 = Class              #21            // java/lang/Object#15 = Class              #22            // java/lang/Throwable#16 = Utf8               SourceFile#17 = Utf8               SyncDemo.java#18 = NameAndType        #7:#8          // "<init>":()V#19 = NameAndType        #5:#6          // i:I#20 = Utf8               SyncDemo#21 = Utf8               java/lang/Object#22 = Utf8               java/lang/Throwable
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public void incr();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=3, args_size=10: aload_01: dup2: astore_13: monitorenter4: aload_05: dup6: getfield      #2                  // Field i:I9: iconst_110: iadd11: putfield      #2                  // Field i:I14: aload_115: monitorexit16: goto          2419: astore_220: aload_121: monitorexit22: aload_223: athrow24: returnException table:from    to  target type4    16    19   any19    22    19   anyLineNumberTable:line 5: 0line 6: 4line 7: 14line 8: 24StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class SyncDemo, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4
      }
      SourceFile: "SyncDemo.java"
      xrl@xrldeMacBook-Air nacos % open/
      The file /Users/xrl/nacos/。 does not exist.
      xrl@xrldeMacBook-Air nacos % open ./ 
      xrl@xrldeMacBook-Air nacos % javac SyncDemo.java         
      xrl@xrldeMacBook-Air nacos % javap -p -v -c SyncDemo.class
      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 276 bytesMD5 checksum ef45d44f86eef93281fefeda549e1283Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#14         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#15         // SyncDemo.i:I#3 = Class              #16            // SyncDemo#4 = Class              #17            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               SourceFile#13 = Utf8               SyncDemo.java#14 = NameAndType        #7:#8          // "<init>":()V#15 = NameAndType        #5:#6          // i:I#16 = Utf8               SyncDemo#17 = Utf8               java/lang/Object
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public synchronized void incr();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 5: 0line 6: 10
      }
  2. 由字节码可知synchronized修饰的方法,并没有出现monitorenter指令和monitorexit指令,取得代之的是:flags: ACC_PUBLIC之后增加了一个ACC_SYNCHRONIZED标识。这个标识指明了当前方法是一个同步方法,JVM通过这个ACC_SYNCHRONIZED访问标志,来辨别一个方法是否为同步方法,从而执行相应的同步调用;

JVM对synchronized的优化

锁状态

JDK1.6之后,synchronized锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁;随着线程的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级一般是单向的,也就是说只能从低到高升级,通常不会出现锁的降级(只针对用户线程,在STW可能会发生锁降级)。

无锁状

  1. new一个对象时,会默认启动匿名偏向锁,但为了避免JVM在启动阶段大量创建对象,从而导致偏向锁竞争过多影响性能,则在JVM启动阶段会次用延迟偏向锁策略,也就是等待一定时间后再开启偏向锁,默认为4,可通过-XX:BiasedLockingStartupDelay = xx设置;
  2. 对于一个新创建的对象,由于在没有成为真正偏向锁之前,对象头markword中的线程ID会一直为空,这种被称为概念上的无锁对象,但markword的锁标识为101

偏向锁

  1. 为了减少同一线程获取锁的代价,如CAS操作带来的耗时等;
  2. 核心思想如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作;
  3. 操作流程
    1. Load-and-test,就是简单判断一下当前线程id是否与Markword中的线程id是否一致;
    2. 如果一致,则说明此线程持有的偏向锁,没有被其他线程覆盖,直接执行后续代码;
    3. 如果不一致,则要检查一下对象是否还属于可偏向状态,即检查“是否偏向锁”标志位;
    4. 如果还未偏向,则利用CAS操作来竞争锁,再次将ID放进去,即重复第一次获取锁的动作;
  4. 偏向锁主要是在锁竞争不激烈的场合对性能的提升,但是对于锁竞争比较激烈的场合,偏向锁作用就很小了,甚至成为累赘,可以通过XX:-UseBiasedLocking命令关闭;
撤销过程
  1. 在一个安全点停止拥有锁的线程;
  2. 遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状;
  3. 唤醒当前线程,将当前锁升级成轻量级锁;
膨胀过程
  1. 当首个线程进程尝试获取锁时,会通过CAS操作,将自己的threadID设置到MarkWord中,如果设置成功,则证明拿到偏向锁
  2. 当线程再次尝试获取锁时,发现自己的线程ID和对象头中的偏向线程ID一致,则在当前线程栈的lock record锁记录中添加一个空的Displaced Mark Word(表示的是原先持有偏向锁的线程ID和相关信息被移动到哪里的标记),并不需要CAS操作;
  3. 重新偏向,当其他线程进入同步块时,发现偏向线程不是自己,则进入偏向锁撤销的逻辑;当达到全局安全点时,如果发现偏向线程挂了,那就把偏向锁撤销,并将对象头内的MarkWord修复为无锁状态,自己尝试获取偏向锁;
  4. 可如果原本的偏向线程还存活,重新偏向失败后,锁开始膨胀为轻量级锁,原来的线程仍然持有锁

轻量级锁

锁膨胀过程
  1. 根据markWork判断是否有线程持有锁,如果有则在当前线程栈中创建一个lock record复制markWord,并通过CAS将当前线程栈的lock record地址放入对象头中,如果成功则说明获取到轻量级锁
  2. 如果失败则说明锁已经被其他线程持有了,此时记录线程的重入次数(把ock recordmarkword设置为null),并进入自适应自旋
  3. 如果自旋到一定的次数后还未获取到锁,则说明目前竞争较重,则膨胀为重量级锁
自旋
  1. 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环;
    1. 存在的问题
      1. 若加锁代码执行慢或锁竞争激烈,则需要等待较长时间,会导致CPU空转,消耗大量CPU资源;
      2. 非公平锁,若存在多个线程自旋等待同一把锁,可能对导致有些线程一致获取不到锁;
    2. 解决办法:通过-XX:PreBliockSpin给线程空循环设置一个次数,默认是10次,或者自旋线程数超过CPU核数的一半时,锁会再次膨胀升级为重量级锁;
  2. 自适应自旋锁:线程空循环次数不固定,而是会根据实际情况动态调整;
    1. 在大部分情况下,已经获取到锁的线程再次尝试获取锁,大概率能成功拿到该锁,所以虚拟机会延长这个线程的自旋次数;

重量级锁

  1. 当出现较大竞争锁膨胀为重量级锁时,对象头的markword指向堆中的monitor,此时线程会封装为一个ObjectWaiter对象,并插入到monitor_cxq竞争队列中然后挂起线程,等待锁释放;
  2. 当持有锁的线程释放后,会将_cxq竞争队列中的ObjectWaiter对象,移到EntryList中,并随机挑选一个对象也就是一个线程唤醒,被选中的线程称之为Heir Presumptive假定继承人,之后Heir Presumptive会去尝试获取锁,但在这段期间其他线程页可能尝试获取锁,所以Heir Presumptive不一定可以获取到锁,当没有获取到锁后那么它会退回到等待队列中,成为EntryList中的最后一个对象;
  3. 当线程获取到锁后,调用Object.wait()方法后,会将线程加入到WaitSet中,当被Object.notify()唤醒后,会将线程从WaitSet移动到_cxqEntryList中去,并且由于Object.wait()、Object.notify()方法主要依赖于Monitor对象实现的,所以锁对象调用这两个方法时的锁状态如果为偏向锁或轻量级锁,则会先膨胀成重量级锁;

锁膨胀过程

  1. 无锁态JVM启动后-XX:BiasedLockingStartupDelay(默认四秒)内的普通对象和四秒后的匿名偏向锁对象;
  2. 偏向锁:只有一个线程进入临界区;
    1. 偏向锁未开启:直接膨胀为轻量级锁
      1. MarkWord中锁标识位信息除外的其他所有信息copy到自己的栈内存的Lock Record中,再尝试通过CASMarkWord中的ptr_to_lock_record指向自己的栈内Lock Record,替换成功则获取到锁,没有则继续自旋;
    2. 匿名偏向锁
      1. MarkWord中锁标识位信息除外的其他所有信息copy到自己的栈内存的Lock Record中,并尝试通过CAS将自己的线程ID设置到MarkWord中;这个线程,后续再次使用这个锁时,无需进行加锁和锁释放操作,只需要在Lock Record添加一个空的MarkWord
      2. 重新偏向:当尝试获取锁时,发现线程ID与MarkWord中记录的线程ID不相同,则进入偏向锁撤销的逻辑;当达到全局安全点时,发现之前持有锁的线程执行完毕,则会发生偏向锁撤销(清除锁记录回归无锁态),然后线程可以通过CAS将自己重新设置到MarkWord
      3. 重新偏向失败:锁膨胀为轻量级锁,尝试通过CASMarkWord中的ptr_to_lock_record指向自己的栈内Lock Record,替换成功则获取到锁,没有则继续自旋;
    3. 调用Object.wait()方法,直接膨胀为重量级锁
  3. 轻量级锁多个线程交替进入临界区
    1. 当出现重度竞争、耗时过长、自旋过多等情况时会膨胀为重量级锁;
      1. 自旋线程数超过CPU核数的一半;
      2. 自旋超过-XX:PreBliockSpin默认10次;
    2. 调用Object.wait()方法,直接膨胀为重量级锁;
  4. 重量级锁:多个线程同时进入临界区;

锁状态的内存布局分析

  1. 引入架包:

    <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.16</version>
    </dependency>
    
  2. MarkWord结构:

    在这里插入图片描述

  3. 运行代码:

    package com.xrl.test;import org.openjdk.jol.info.ClassLayout;/*** @version [v1.0]* @author: [xrl]* @create: [2024/05/21 16:14]**/
    public class ObjectHead {public String str;public static void main(String[] args) throws InterruptedException {/**无锁态:虚拟机刚启动时 new 出来的对象处于无锁状态**/ObjectHead obj = new ObjectHead();System.out.println(ClassLayout.parseInstance(obj).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000000000001 (non-biasable; age: 0) // 对象头 non-biasable (转化为二进制最后三位表示,锁标志位状态:001)8   4                    (object header: class)    0xf800c105                               // ClassPointer指针12   4   java.lang.String ObjectHead.str            null                                    // 成员变量Instance size: 16 bytes                                                                     // 共占用16个字节Space losses: 0 bytes internal + 0 bytes external = 0 bytes total*//**轻量级锁:对于真正的无锁态对象obj加锁之后的对象处于轻量级锁状态**/synchronized (obj) {// 查看对象内部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000309dcd9d8 (thin lock: 0x0000000309dcd9d8) // (转化为二进制最后三位表示,锁标志位状态:000)8   4                    (object header: class)    0xf800c10512   4   java.lang.String ObjectHead.str            nullInstance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total*/}/**匿名偏向锁:休眠4S后再创建出来的对象处于匿名偏向锁状态**/Thread.sleep(4000);ObjectHead obj1 = new ObjectHead();System.out.println(ClassLayout.parseInstance(obj1).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000000000005 (biasable; age: 0)  //(转化为二进制最后三位表示,锁标志位状态:101)8   4                    (object header: class)    0xf800c10512   4   java.lang.String ObjectHead.str            nullInstance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total*//**重量级锁:调用wait方法之后锁对象直接膨胀为重量级锁状态**/new Thread(() -> {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}).start();Thread.sleep(1);synchronized (obj) {// 查看对象内部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable());/*** com.xrl.test.ObjectHead object internals:* OFF  SZ               TYPE DESCRIPTION               VALUE*   0   8                    (object header: mark)     0x00007fe748016c0a (fat lock: 0x00007fe748016c0a) (转化为二进制最后三位表示,锁标志位状态:010)*   8   4                    (object header: class)    0xf800c105*  12   4   java.lang.String ObjectHead.str            null* Instance size: 16 bytes* Space losses: 0 bytes internal + 0 bytes external = 0 bytes total*/}}
    }
    /*** 抛出异常原因:违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(Obj)的监控器或者通知其他线程等待该对象(Obj)的监控器时,抛出该异常* Exception in thread "Thread-1" java.lang.IllegalMonitorStateException* at java.lang.Object.wait(Native Method)* at java.lang.Object.wait(Object.java:502)* at com.xrl.test.ObjectHead.lambda$main$0(ObjectHead.java:53)* at java.lang.Thread.run(Thread.java:750)*/
    

同步消除

  1. Java虚拟机在编译代码时,通过会对运行上下文进行扫描,从而去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的获取锁开销;

锁的可重入

  1. 一个线程获得一个对象锁后,运行他再次请求该对象锁;

  2. synchronized是基于monitor实现的,每次重入monitor中的计数器会加一;

  3. 当子类继承父类时,子类可以通过可重入锁,调用父类的同步方法;

    class Parent {public synchronized void parentMethod() {System.out.println("Parent method");}
    }class Child extends Parent {public synchronized void childMethod() {System.out.println("Child method");parentMethod(); // 在子类中调用父类的同步方法}
    }public class Main {public static void main(String[] args) {Child child = new Child();child.childMethod();}
    }
    

其他机制

等待/唤醒机制

  1. wait()、notify()、notifyAll()这三个方法在使用时,必须处在synchronized代码块或方法中,否则会抛出IllegalMonitorStateException异常,这是由于这三个方法都依赖于monitor对象实现这也是三个方法处在Object对象中的原因,而synchronized关键字决定着一个JAVA对象会不会生成monitor对象;
  2. wait()、sleep()方法的区别:
    1. wait()方法会释放当前持有的锁,并将线程移入waitSet中;
    2. sleep()方法只会让线程休眠并不会释放锁(类似于执行for(;;){}死循环);

线程中断机制

  1. JDK1.2遗弃Thread.stop()后,JAVA就没有提供强制性停止执行中线程的方法,而是提供了协调式的方式

    //中断线程(实例方法)
    public void Thread.interrupt();
    //判断线程是否被中断(实例方法)
    public boolean Thread.isInterrupted();
    //判断是否被中断并清除当前中断状态(静态方法)
    public static boolean Thread.interrupted();
    
  2. 使用:我们可以通过调用Thread.interrupt()来进行线程中断,但由于线程中断是协调式的,所以他并不会去停止线程,而是需要我们手动进行中断检测并结束线程;并且当线程处于阻塞状态或者尝试执行一个阻塞操作时,我们调用线程中断方法,执行中断操作后会抛出InterruptedException异常;

        public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(){@Overridepublic void run(){try {// 判断当前线程是否已中断,执行后会对中断状态进行复位while (!Thread.interrupted()) {System.out.println("1111");// 线程阻塞,抛出异常// TimeUnit.SECONDS.sleep(1);}System.out.println("线程中断");// 输出false 说明中断状态复位System.out.println(this.isInterrupted());} catch (Exception e) {System.out.println(e);}}};t1.start();TimeUnit.SECONDS.sleep(2);// 中断线程t1.interrupt();}
    
  3. synchronized与线程中断:对于synchronized而言,一个线程的执行只有两种状态,一种是获取了锁正在执行,一种是没获取到锁在阻塞额等待;那么他们即使调用中断线程的方法,也不会生效;

synchronized为什么不禁止指令重排序

  1. synchronized是通过互拆锁的方式来保证线程安全的,它的本质是将多线程并行执行变成单线程的串行执行,而指令重排序可能导致的问题是多线程环境程序乱序执行的问题,所以指令重排序对synchronized而言并不会存在乱序问题,反而可以提升串行执行时的性能;

synchronized性能不佳的原因

  1. synchronized是基于进入和退出Monitor管程实现的,而Monitor底层时依赖于操作系统的Mutex Lock,所以在其获取锁或者释放锁的时候都需要经过操作系统的调用,会涉及到频繁的用户态与内核态之间的切换,从而导致性能不佳;
  2. 但在并发竞争不高的情况下,由于synchronized几种锁状态、锁消除等技术的优化,synchronized性能并不差;

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

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

相关文章

昇思25天学习打卡营第4天|数据集Dataset

数据集 Dataset 介绍 之前说过&#xff0c;MindSpore是基于Pipeline&#xff0c;通过Dataset和Transformer进行数据处理。Dataset在其中是用来加载原始数据的。mindSpore提供了数据集加载接口&#xff0c;可以加载文本、图像、音频等&#xff0c;同时也可以自定义加载接口。此…

【UE开发】游戏库存UI系统Demo

1.项目介绍 1.描述&#xff1a;一种用于存储记录玩家物品的游戏内可视化操作系统。 2.演示&#xff1a;https://www.bilibili.com/video/BV1f53neVEfW/?vd_source50dea901fd12253f417c48b937975b0d 3.大纲&#xff1a; 4.样式&#xff1a; 2.W_Inventory_Main_01&#xff08;…

CORE Mobility Errorr的调试

在运行CORE tutorial 3中的mobility示例时&#xff0c;出现如下错误&#xff1a; 当看到这个问题的时候&#xff0c;并没有仔细去分析日志和现象&#xff0c;在core-daemon的进程打印界面只看了一下最后的出错堆栈&#xff1a; 2024-06-27 10:43:48,614 - ERROR - _server:_ca…

MySQL8 新特性——公用表表达式用法 with t1 as (select * from user)

MySQL8 新特性——公用表表达式用法_mysql ctes-CSDN博客 1.普通公用表表达式 MySQL8 新特性——公用表表达式用法 在MySQL 8.0及更高版本中&#xff0c;引入了公用表表达式&#xff08;Common Table Expressions&#xff0c;CTEs&#xff09;&#xff0c;它是一种方便且可重…

docker部署vue项目

1.下载docker desktop软件 Docker Desktop启动的时候&#xff0c;有可能弹框提示"WSL2 installations is incomplete"&#xff0c;这是您的系统中没有安装WSL2内核的原因&#xff0c;打开【https://aka.ms/wsl2kernel ,在打开的页面中有一个Linux内核更新包"链…

【python011】经纬度点位可视化html生成(有效方案)

1.熟悉、梳理、总结项目研发实战中的Python开发日常使用中的问题、知识点等&#xff0c;如获取省市等边界区域经纬度进行可视化&#xff0c;从而辅助判断、决策。 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&#xff01; 3.欢迎点赞、关注、批…

STM32将外部SDRAM空间作为系统堆(Heap)空间

概述 stm32可以外扩很大的sram&#xff0c;常见外部sram的初始化函数一般是c语言写的&#xff0c;默认写在main函数里面。stm32初始化首先进入汇编代码startup_stm32f429xx.s&#xff0c;在汇编代码中Reset_Handler&#xff08;复位中断服务程序&#xff09;里面先调用了Syste…

vue + Lodop 制作可视化设计页面 实现打印设计功能(二)

历史&#xff1a; vue2 Lodop 制作可视化设计页面 实现打印设计功能&#xff08;一&#xff09; 前言&#xff1a; 之前本来打算用直接拿之前做的vue2版本改改就发的&#xff0c;但考虑到现在主流都是vue3了&#xff0c;所以从这篇文章开始使用vue3来写&#xff0c;以及最后…

三相LCL滤波型PWM逆变器仿真设计

参考并网电流外环电容电流前馈内环的双闭环控制结构&#xff0c;在光伏和风力发电网侧变换器中的应用&#xff0c;可以显著提高系统的稳定性和效率。在并网电流外环中&#xff0c;通过检测电网电流并与其参考值进行比较&#xff0c;可以得到一个电流误差信号。这个电流误差信号…

MySQL基础查询与复杂查询

基础查询 1、查询用户信息&#xff0c;仅显示用户的姓名与手机号&#xff0c;用中文显示列名。中文显示姓名列与手机号列。 2、根据商品名称进行模糊查询&#xff0c;模糊查询需要可以走索引&#xff0c;需要给出explain语句。使用explain测试给出的查询语句&#xff0c;需要显…

程序员职业发展指南,如何选择适合自己的就业方向?

随着科技的发展和数字化时代的到来&#xff0c;程序员是IT行业中的热门职业。尤其是近几年移动互联网的迅速发展&#xff0c;IT人才更是紧缺&#xff0c;越来越多的人加入程序员这个行列。 从事程序员工作&#xff0c;如何接项目呢&#xff1f;YesPMP是一个专注于互联网外包的平…

【知识学习】阐述Unity3D中动画渲染的概念及使用方法示例

Unity3D中的卡通渲染&#xff08;Cartoon Rendering&#xff09;是一种渲染技术&#xff0c;它模仿传统手绘动画或漫画的视觉效果。这种渲染风格通常具有鲜明的颜色、清晰的轮廓线和简化的光影效果&#xff0c;常用于制作动画、游戏和其他视觉媒体。 卡通渲染的基本概念 轮廓…

<sa8650>QCX ISP Tuning 使用详解 — Tuning前置条件

<sa8650>QCX ISP Tuning 使用详解 — Tuning前置条件 一 如何安装 Qualcomm Chromatix™ 摄像头校准工具二 如何使用 Qualcomm Chromatix™ tuning工具创建tuning项目2.1 创建工程前提依赖2.2 创建工程2.3 添加场景2.4 编辑区域触发器三 如何创建Tuning 树一 如何安装 Qualco…

postman教程-22-Newman结合Jenkins执行自动化测试

上一小节我们学习了Postman Newman运行集合生成测试报告的方法&#xff0c;本小节我们讲解一下Postman Newman结合Jenkins执行自动化测试的方法。 在软件开发过程中&#xff0c;持续集成&#xff08;CI&#xff09;是一种实践&#xff0c;旨在通过自动化的测试和构建过程来频繁…

【高等数学】一元函数积分及其应用:定积分与反常积分

文章目录 第一节. 定积分一. 定积分的概念1. 定义2. 定积分存在定理3. 定积分的几何意义与求解 二. 定积分的性质1. 不等式2. 中值定理 三. 积分上限&#xff08;为x&#xff09;函数1. 积分上限函数定义2. 积分函数求导3. 积分函数的奇偶性变化 四. 定积分的计算 第二节. 反常…

vue的ESLint 4格缩进 笔记

https://chatgpt.com/share/738c8560-5271-45c4-9de0-511fad862109 一&#xff0c;代码4格缩进设置 .eslintrc.js文件 module.exports { "rules": { "indent": ["error", 4] } }; 自动修复命令 npx eslint --fix "src/**/*.{…

作为图形渲染API,OpenGL和Direct3D的全方位对比。

当你在网页看到很多美轮美奂的图形效果&#xff0c;3D交互效果&#xff0c;你知道是如何实现的吗&#xff1f;当然是借助图形渲染API了&#xff0c;说起这个不就不得说两大阵营&#xff0c;OpenGL和Direct3D&#xff0c;贝格前端工场在本文对二者做个详细对比。 一、什么是图形…

springboot实习管理系统的设计与实现 LW +PPT+源码+讲解

第三章系统分析与设计 3.1 可行性分析 一个完整的系统&#xff0c;可行性分析是必须要有的&#xff0c;因为他关系到系统生存问题&#xff0c;对开发的意义进行分析&#xff0c;能否通过本系统来补充线下实习管理模式中的缺陷&#xff0c;去解决其中的不足等&#xff0c;通过对…

专业技术!最新氧化物异质结纳米制备技术

网盘 https://pan.baidu.com/s/1vjO2yLxm638YpnqDQmX7-g?pwd3at5 MOF衍生的B_A_B结构氧化物异质结及其制备方法和应用.pdf 二硫化钼-硫化镉纳米复合材料及其制备方法和应用.pdf 具有异质界面的耐辐照复合薄膜及其制备方法与应用.pdf 基于异质结双界面层纳米材料的复合介电薄膜…

【软考论文】项目背景及论文模版

目录 一、项目核心功能二、论文模板一、项目核心功能 二、论文模板 论文字数说明 总字数 2500 = 500 + 400 +400 * 3 + 300 背景:500 回答问题:400 三段论:1200 = 400 * 3 结论:300 ~ 400 摘要(<300字) 本人于2022年1月参与了某车厂的全渠道数字化精准营销平台项目,该…