并发编程笔记四:Monitor原理、synchronized原理、synchronized 原理进阶

Monitor原理

Monitor 被翻译为监视器或管程
每个java对象都可以关联一个Monitor对象,如果使用synchronized给对像上锁(到重量锁级别)之后,该对象的MarkWork中的值被设置指向Monitor对象的指针。
Monitor结构
在这里插入图片描述

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入
    EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,这是线程的竞争是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足(调用了wait())进入 WAITING 状态的线程,这是需要notify/notifyAll唤醒。
    注意点:
  • 上述效果是synchronized必须是同一个对象,monitor才有上述的效果
  • 没有通过synchronized的对象是不会关联monitor的

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;)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引用 -> slot 1
 5:monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
 6:getstatic #3 // <- i
 9:iconst_1 // 准备常数 1
 10:iadd // +1
 11:putstatic #3 // -> i
 14:aload_1 // <- lock引用
 15:monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
 16:goto 24
 19:astore_2 // e -> slot 2 
 20:aload_1 // <- lock引用
 21:monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
 22:aload_2 // <- slot 2 (e)
 23:athrow // throw e
 24:return
    Exception table:
    from to
    target type
 6 16 19any
 19 22 19any
    LineNumberTable:
    line 8:0
    line 9:6
    line 10:14
    line 11:24
    LocalVariableTable:
    Start Length
    Slot Name
    Signature
 0 25 0     args   [Ljava/lang/String;
    StackMapTable:number_of_entries =2
    frame_type =255 /* full_frame */
    offset_delta =19
    locals =[class "[Ljava/lang/String;",

    class java/lang/Object ]
    stack =[

    class java/lang/Throwable ]
    frame_type =250 /* chop */
    offset_delta =4

在该字节码中

 5:monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
 6:getstatic #3 // <- i
 9:iconst_1 // 准备常数 1
 10:iadd // +1
 11:putstatic #3 // -> i
 14:aload_1 // <- lock引用
 15:monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList

这便是java代码块中的同步代码块的部分,也就是被synchronized修饰的部分代码块。
这也说明synchronized底层就是使用monitor对象关联来实现的。

synchronized 原理进阶

在jdk1.6之后,synchronized锁被优化,有一个锁升级的过程。

synchronized锁对象升级

过程分四个阶段:无锁–>偏向锁–>轻量级锁–>重量级锁

无锁: 新创建的对象,第一次被sychronized修饰为锁对象,当时该对象的后三位仍然是001,这是因为sychronized对偏向锁设置有个延时过程。需要过几秒才会变为偏向状态101
可以通过XX:BiasedLockingStartupDelay=0 来禁用延迟。

偏向锁: jvm记录只有第一次使用的cas来将线程ID设置到对象的MarkWord头,之后再次需要获得该对象锁时发现这个线程ID是自己的就表示没有竞争,不用重新cas。只要不要说竞争,之后这个对象锁就一直属于该线程,降低了开销。偏向锁后三位为101

轻量级锁: synchronized优化经过偏向锁阶段后,这时又来新线程进行竞争该锁对象,新的线程是通过cas去进行自旋竞争该对象锁。若是cas自旋获得该对象锁,则当前对象锁升级为轻量级锁,轻量级的对象锁对象头设置拥有其的线程ID,后两位为00
重量级锁: 锁膨胀(锁粗化)的过程,当cas自旋获取对象锁失败,如果是其他线程已经持有该对象锁,这时表明有竞争,当前对象锁升级为对象锁。这是就需要引用monitor对象,锁对象的对象头设置其对应的monitor对象指针,后两位为11

四种锁对象头对应表
在这里插入图片描述

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID。
当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。

批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。

锁消除

锁消除,时jvm中的JIT(即时编译器)可以识别到,当前锁对象一定不会被竞争重用时,就会将锁的逻辑不在底层编译出来。
如下,局部变量o时不能被重用的,因为没错调用b()方法,都会创建新的o对象,所以这时就会有锁消除。

public class MyBenchmark {
    static int x = 0;

    public void a() throws Exception {
        x++;
    }

    public void b() throws Exception {
        Object o = new Object();
        synchronized (o) {
            x++;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值