Java并发编程-synchronized底层原理

synchronized底层原理与Monitor密切相关

1.Java对象头

以 32 位虚拟机为例
普通对象
在这里插入图片描述
对象的类型,如Student类型,Teacher类型等是由KlassWord来表示的,它是一个指针,指向了对象所从属的Class。即找到类。
其中 Mark Word 结构为
在这里插入图片描述
其中,age表示垃圾回收时的分代年龄,具体可见分代收集算法。年龄超过一定岁数就从幸存区调入到老年代。最后两位,biased_lock和01表示偏向锁和加锁状态。其它表示在不同状态下每一位所代表的含义。Normal状态表示对象的正常状态。对它加各种所的时候,其值通常会改变。

数组对象
在这里插入图片描述

注意:所以,这里我们也可以想到,在一个Integer对象里,要保存8个字节的对象头,还有4个字节的int类型的数据。总共12字节,而基本数据类型int则只需4个字节。在内存敏感的情况下,建议用基本类型。

2.Monitor(锁)工作原理

synchronized底层原理可以用Monitor工作原理来解释
Monitor被翻译为监视器或者管程

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

在这里插入图片描述
当我们线程2执行如下代码时:

synchronized(this){
    
    
//处理相关业务
}

1.Thread2线程执行上述代码时,当前对象this会被上一把锁,这是一把重量级锁,this对象头的Mark Word字段指向了操作系统创建的Monitor对象引用地址
MarkWord在没有加任何锁的时候,即Normal状态,标记位为01,一旦获取了锁,就会尝试找一个monitor与之关联,然后把最后两位也即标记位从01改为10,并且把前面的所有位改成指向monitor对象的指针,占用30位。此时由于只有Thread2线程,所以成功获取了锁。理所应当的成为了monitor对象的owner。
owner表示Monitor锁的持有者,而且同一个时刻只能有一个owner

2.Monitor对象只能有一个owner,此时如果有其它线程如Thread-3或Thread-4等线程要获取这把锁就要进入Monitor对象的堵塞队列EntryList中等待Thread2释放锁
EntryList可以理解位阻塞队列或等待队列。一直等待到其它线程释放了owner的所有权

3.等待锁资源被释放后,Thread-3或Thread-4会互相竞争锁资源,并不能保证谁获取到锁,最终还是有CPU来决定

4.Monitor对象的WaitSet存放的是,获取到锁的线程,但是由于其它一些原因导致线程进入Waiting状态,又释放了锁资源,待介绍

同样地,对于下图
在这里插入图片描述
1.刚开始 Monitor 中 Owner 为 null
2.当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
3.在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList BLOCKED
4.Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
5.图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲wait-notify 时会分析

synchronized 必须是进入同一个对象的 monitor 才有上述的效果
不加 synchronized 的对象不会关联监视器,不遵从以上规则

3.字节码角度理解synchronized底层工作原理

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

反编译成字节码,具体反编译过程及字节码指令介绍可以参看JVM学习-字节码指令
字节码如下:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2                  //<-lock引用(synchronized开始)
         3: dup
         4: astore_1						  //lock引->slot1
         5: monitorenter					  //将lock对象Markword置为monitor指针
         6: getstatic     #3                  // Field counter:I
         9: iconst_1
        10: iadd
        11: putstatic     #3                  // Field counter:I
        14: aload_1							  //<-lock引用,拿到刚开astore1刚才存储的临时变量
        15: monitorexit						  //将lock对象MarkWord重置,唤醒EntryList.重新设置markword其它字段,让entryList中正在等待的线程竞争锁
        16: goto          24				 
   /*
   如果同步代码块中发生了异常,然后就处理下列代码,即19-23行。
   最下面的Exception table表第一行时监测6-16行是否发生了异常,即同步代码块中的代码。
   如果发生了异常,就跳转到19行执行。
   先把异常对象存储进来,然后根据对象引用地址找到monitor,然后也是做一些善后的工作。
   把monitor中的状态还原,并唤醒entrylist中的其它线程      
   */
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             6    16    19   any
            19    22    19   any

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/113664973