JUC学习笔记——共享模型之管程( 六 )

  • 如果使用 synchronized 给对象上锁(重量级)之后 , 该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
  • 我们给出简单示例图:
    JUC学习笔记——共享模型之管程

    文章插图
    我们来做简单解释:
    • 每个obj都由MarkWord来绑定一个Monitor
    • 每个线程都需要经过synchronized(obj)方法进入Monitor
    • 首先Monitor主要分为三个部分:WaitSet,EntryList,Owner
    • Owner:属于当前Monitor的正常运行区间,例如Thread-2就是目前运行线程
    • EntryList:属于当前Monitor的等待运行区间 , 需要等到Thread-2结束线程释放锁资源,Thread3等才可以抢夺锁
    • WaitSet:属于之前获得过锁,但条件不满足进入 WAITING 状态的线程 , 后面讲 wait-notify 时会分析
    此外我们还需要注意:
    • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
    • 不加 synchronized 的对象不会关联监视器,不遵从以上规则
    synchronized锁这小节我们将会介绍synchronized底层原理和相关锁的内容
    synchronized原理我们会从底层代码来讲解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=1// 下面是正式字节码过程// 正常运行阶段0: getstatic #2 // <- lock引用 (synchronized开始)3: dup4: astore_1 // lock引用 -> slot 1(这里提前存储一份锁对象,用于后续的解锁)5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针(这里进行了上锁)6: getstatic #3 // <- i9: iconst_1 // 准备常数 110: iadd // +111: putstatic #3 // -> i14: aload_1 // <- lock引用15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList(这里进行了解锁)16: goto 24// 报错补救阶段(去除锁)19: astore_2 // e -> slot 220: aload_1 // <- lock引用21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList22: aload_2 // <- slot 2 (e)23: athrow // throw e// 代码结束24: return// 这里是异常检测器:当出现异常时,移动到异常处理底层代码区域进行解锁操作Exception table:from to target type61619any192219anyLineNumberTable:line 8: 0line 9: 6line 10: 14line 11: 24LocalVariableTable:Start Length Slot Name Signature0250args [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轻量级锁我们首先来简单介绍一下轻量级锁:
    • 如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化 。
    我们用一个简单的代码实现轻量级锁:
    // 下面两个代码都调用了锁,但是他们归根结底属于一个流程,时间错开,这时系统就会避免直接使用Monitor而是用轻量级锁进行优化static final Object obj = new Object();public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}}public static void method2() {synchronized( obj ) {// 同步块 B}}我们来展示一下实现流程(00轻量级锁,01无锁):
    1. 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

    JUC学习笔记——共享模型之管程

    文章插图
    1. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

    JUC学习笔记——共享模型之管程

    文章插图
    1. 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

    JUC学习笔记——共享模型之管程

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

    JUC学习笔记——共享模型之管程

    文章插图
    1. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    除此之外我们需要注意cas切换不是每次都成功的: