• 【字节码⻆度看synchronize】三种应用方式、Monitor类、monitorenter、monitorexit_JUC15


    1、从字节码⻆度分析synchronized实现

    1)早期的synchronized效率低的原因
    • 1.synchronized属于重量级锁效率低下,是因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的
    • 2.挂起线程和恢复线程需要转⼊内核态去完成,这种状态切换需要耗费处理器时间
    • 3.阻塞或唤醒一个Java线程,需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间。
    • 4.时间成本相对较⾼,这也是为什么早期的synchronized效率低的原因。
    • 5.后期进⾏了锁的优化锁升级,对应偏向锁、轻量级锁的讲解会在下⼀节进⾏叙述。
      在这里插入图片描述
    2)synchronized有三种应⽤⽅式
    • 1.作⽤于代码块:对括号⾥的对象加锁
    • 2.作⽤于实例⽅法:当前实例加锁,进⼊同步代码前要获得当前实例的锁 。
    • 3.作⽤于静态⽅法:当前类加锁,进去同步代码前要获得当前类对象的锁。
    3)synchronized同步代码块
    • 实现使用的是monitorenter和monitorexit指令

    在这里插入图片描述

    ①. 一定是一个enter和两个exit吗?
    • 不一定,如果方法中直接抛出了异常处理,那么就是一个monitorenter和一个monitorexit
    4)synchronized普通同步方法
    • 1.调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置
    • 2.如果设置了,执行线程需要先持有monitor然后才能执行方法,最后等方法完成(无论是正常完成还是非正常完成)时释放minotor
      在这里插入图片描述
    5)synchronized静态同步方法
    • ACC_STATIC和ACC_SYNCHRONIZED访问标志,区分该方法是否静态同步方法
      在这里插入图片描述

    2、反编译synchronized锁的是什么东西?

    1)ObjectMonitor
    • 任何一个对象都可以成为一个锁,在HotSpot虚拟机中,monitor采用ObjectMonitor实现的
      在这里插入图片描述
    2)阿里开发手册说明
    • 高并发时,同步调用应该去考量锁的性能损耗
      • 1.能用无锁,就不要用锁
      • 2.能锁区块,就不要锁整个方法体
      • 3.能用对象锁,就不要用类锁

    3、Monitor类

    1)前⾔
    • 1.在Java中万物皆是对象,所以这些指令(monitorenter、 monitorexit)会不会和某些对象有关系呢? 果然可以和Monitor类联系到⼀块
    • 2.monitor相当于⼀个对象的钥匙,只有拿到此对象的monitor,才能访问该对象的同步代码。 相反未获得monitor的只能阻塞来等待持有monitor的线程释放monitor。可以这样⽐喻吧,monitorenter 和monitorexit 对应的就是拿钥匙和还钥匙
    2)Monitor与java对象以及线程是如何关联 ?
    • 1.⾸先,每⼀个对象都有⼀个属于⾃⼰的monitor,其次如果线程未获取到singal (许可),则线程阻塞。objec可以⽐作医院的诊室,monitor 就是负责喊病⼈的护⼠,线程则是就诊的病⼈。
    • 2.通过护⼠(监视器)的调度,诊室(synchronized锁住的对象)内只允许进⼊⼀个病⼈(线程),此病⼈(线程)在当前时间就拥有此诊室(对象)的使⽤权,也就是获取了许可。病⼈就诊完毕,则表明归还了诊室的使⽤权。然后护⼠再调度下⼀个等待的病⼈进⼊诊室(被阻塞的线程)。⾛廊当中等待的病⼈们。

    4、monitorenter

    1)概述:
    • 1)每⼀个对象都会和⼀个监视器monitor关联。监视器被占⽤时会被锁住,其他线程⽆法来获取该monitor。当JVM执⾏某个线程的某个⽅法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权
    • 2)synchronized的锁对象会关联⼀个monitor,这个monitor不是我们主动创建的,是JVM的线程执⾏到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当⼀个线程拥有monitor后其他线程只能等待
    2)其过程如下:
    • 1.若monior的进⼊数为0,线程可以进⼊monitor,进入后将monitor的进数置为1。当前线程成为monitor的owner(所有者) 。
    • 2.若线程已拥有monitor的所有权,允许它重⼊monitor,则进⼊monitor的进⼊数再加1
    • 3.若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞直到monitor的进⼊数变为0,才能重新尝试获取monitor的所有权

    5、monitorexit

    1)概述
    • 1.能执⾏monitorexit指令的线程,⼀定是拥有当前对象的monitor的所有权的线程
    • 2.执⾏monitorexit时会将monitor的进⼊数减1。当monitor的进⼊数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权
    2)monitorexit释放锁
    • 1.monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出释放锁

    • 2.monitorexit释放锁monitorexit插⼊在⽅法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。

    3)面试题synchroznied出现异常会释放锁吗?
    • 会释放锁

    6、synchronized同步方法

    1)synchronized普通同步方法
    • 调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程需要先持有monitor然后才能执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放minotor

    在这里插入图片描述

    2)分析:
    • 1.从编译的结果来看方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的。
    • 2.两种同步方式本质上没有区别只是方法的同步是一种隐式的方式来实现无需通过字节码来完成
    • 3.两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现。被阻塞的线程会被挂起、等待重新调度,会导致"用户态和内核态"两个态之间来回切换,对性能有较大影响。
  • 相关阅读:
    成为一个合格程序员所必备的三种常见LeetCode排序算法
    自定义Mybatis-plus插件(限制最大查询数量)
    spring bean实例注入到map 集合中
    网络安全笔记 -- XSS跨站(原理、分类)
    IDEA 集成Maven
    C++11多线程std::thread入门使用以及对比分析pthread
    java计算机毕业设计小型医院药品及门诊管理源程序+mysql+系统+lw文档+远程调试
    C++入门
    备忘录:Docker基础操作与常用命令
    UML 用户指南
  • 原文地址:https://blog.csdn.net/weixin_38963649/article/details/126143106