synchronized 的 monitor 机制

前言

  1. 本文基于 jdk 8 编写。
  2. @author JellyfishMIX - github / blog.jellyfishmix.com
  3. LICENSE GPL-2.0

monitor

  1. monitor 是 synchronized 中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 class 持有的锁。每一个对象有且仅有一个 monitor。

  2. 这也是为什么会看到 synchronized(object) 或 synchronized(MainActivity.class) 这样的写法,这样指明了使用了什么对象或类持有的 monitor。

  3. 下图描述了线程和 monitor之间关系,以及线程的状态转换图。

    img

  4. 如上图,每个 monitor 在每个时刻,只能被一个线程持有,该线程就是 activeThread,其它线程都是 waitingThread,分别在两个队列 entryList 和 waitSet 里等候。在 entryList 中等待的线程状态是 waiting for monitor entry,而在 wait set 中等待的线程状态是 in Object.wait()。

entryList

  1. 先看 entryList 里面的线程。我们称被 synchronized 包起来的代码段为临界区。

    synchronized(object) {
       // ......
    }
    
  2. 当一个线程尝试进入临界区时,有两种可能性:

    1. 该 monitor 不被其它线程持有,entryList 里面也没有其它等待线程。尝试获取 monitor 的当前线程即成为对应对象或类的 monitor 的 owner,执行临界区的代码。

    2. 该 monitor 被其它线程拥有,尝试获取 monitor 的当前线程在 entryList 队列中等待并休眠。

    3. 第一种情况下,线程将处于 Runnable 的状态。第二种情况下,线程 dump 输出信息会显示处于 waiting for monitor entry。如下:

      "Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8] 
      at testthread.WaitThread.run(WaitThread.java:39) 
      - waiting to lock <0xef63bf08> (a java.lang.Object) 
      - locked <0xef63beb8> (a java.util.ArrayList) 
      at java.lang.Thread.run(Thread.java:595) 
      
  3. 临界区的机制,是为了保证其内部代码执行的原子性,消除竞态条件。

  4. 需要注意的是,临界区在任何时间只允许线程串行通过,这和我们使用多线程提高性能的初衷是相反的。如果在多线程的程序中,大量使用 synchronized,或者不恰当地使用了它,会造成大量线程在临界区的入口等待获取 monitor,造成性能大幅下降。通过 dump 排查问题时,这也是要排查的点。

  5. 排查线程阻塞的问题时,cpu 忙则关注 Runnable 的线程,可能正在运行的线程数量太多了,比如线程池 coreSize 设置的太大了。cpu 闲则关注 dump 输出信息中的 waiting for monitor entry,可能大量线程阻塞在获取 monitor。只有获得了 monitor 的线程在占用 cpu 时间片运行,cpu 利用率低。

waitSet

  1. 当线程获得了 monitor,进入临界区之后,如果代码编写调用了 monitor 对应的对象(一般是被 synchronized 修饰的对象)的 wait() 方法,此时放弃 monitor,进入 waitSet 队列并休眠。

  2. 只有别的线程在该对象上调用了 notify() 或 notifyAll() 方法,waitSet 队列中的线程才进入 entryList 中等待竞争。但是只有一个线程能获得此对象的 monitor,线程状态恢复到 Runnable,其他线程继续在 entryList 中等待并休眠。

  3. 在 waitSet 中的线程,dump 输出信息中表现为: in Object.wait()。

    "Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38] 
     at java.lang.Object.wait(Native Method) 
     - waiting on <0xef63beb8> (a java.util.ArrayList) 
     at java.lang.Object.wait(Object.java:474) 
     at testthread.MyWaitThread.run(MyWaitThread.java:40) 
     - locked <0xef63beb8> (a java.util.ArrayList) 
     at java.lang.Thread.run(Thread.java:595) 
    

synchronized 的锁升级机制(偏向锁, 轻量级锁, 重量级锁)

entryList 的机制被称为重量级锁,性能较差。jdk 1.6 从 JVM 层⾯对 synchronized 进行了⼤优化,引入了偏向锁, 轻量级锁和锁升级机制,提高了 synchronized 的性能。

偏向锁

  1. ObjectHeader 对象头信息中,有一个标记记录了此对象 monitor 的持有线程,如果尝试获取的线程和此对象 monitor 的持有线程相同,则直接获得 monitor,不需要走锁机制。体现了 synchronized 可重入锁的特征。

轻量级锁

  1. 当一个线程尝试进入临界区时,如果获取偏转锁失败,则进行轻量级锁的机制,CAS 自旋尝试抢锁。
  2. 这个设计是为了提高锁的性能,如果在 CAS 自旋期间抢到了锁,则可以减少线程休眠和唤醒的开销。

重量级锁

在这里插入图片描述

  1. 自旋 n 次(默认为10,可以通过 JVM 参数 PreBlockSpin 调整) 如果仍未抢到,则升级为重量级锁。
  2. 线程进入 contentionList, 一个链表,每次通过 CAS 在队头添加 node, 而出队(取线程)操作发生在队尾。
  3. contentionList 会被线程并发访问,为了降低对 contentionList 的并发争用,而引入 entryList。
  4. owner 线程在 unlock 时会从 contentionList 中迁移线程到 entryList,并会指定 entryList 中某个线程(一般是第一个)为 OnDeck 线程(候选线程)。owner 线程并不是把锁传递给 OnDeck 线程,只是把竞争锁的权利交给 OnDeck,OnDeck 线程需要竞争抢锁。这里体现了 synchronized 是非公平锁。
  5. 线程进入 entryList 后。entryList 的机制如本文前部分所述。

题外话 – 锁降级

  1. 当 n 个线程同时竞争变成了重量级锁,n 个线程都执行完毕之后,monitor 对象会变为无锁。
  2. 此时再有一个线程去争抢锁,就从无锁变成了轻量级锁。
  3. 总结: 当重量级锁被所有尝试获取的线程全部释放了之后,monitor 对象是无锁的。有新的线程尝试获取锁时,又会从轻量级锁开始。

参考文章: https://blog.csdn.net/yessimida/article/details/114294497

参考文章

synchronized重量级锁原理1 – 别给我加香菜 – 掘金 https://juejin.cn/post/7180527422235181115

Synchronized实现原理和锁优化 – zhifeng687 – CSDN https://blog.csdn.net/qq_26222859/article/details/53786134

谈谈Java中的锁机制 – 我超爱JAVA的 – CSDN https://blog.csdn.net/Dev_Hugh/article/details/106577862

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JellyfishMIX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值