侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Synchronized 之重量级锁的实现原理

再见理想
2022-05-29 / 0 评论 / 0 点赞 / 392 阅读 / 1,466 字

一,synchronized修饰符对应的字节码指令

synchronized 可以作用在方法上,也可以作用代码块内。

public class SynDemo {

    public synchronized void test(){
        System.out.println("synchronized test!!!");
    }

    public void testBlock(){
        synchronized (this) {
            System.out.println("synchronized test!!!");
        }
    }
}

通过 javap -v SynDemo.class反编译:通过javap命令分析java汇编指令

test()反编译后的代码如下图

testBlock()反编译后的代码如下图

可以看到,synchronized 修饰方法反编译后,输出的字节码有 ACC_SYNCHRONIZED 标识;synchronized 修饰代码块反编译后,输出的字节码有 monitorenter 和 monitorexit 语句。

由此我们知道,synchronized 的作用域不同,JVM 底层实现原理也不同。synchronized 修饰代码块是通过 monitorenter 和 monitorexit 来实现其语义的;synchronized 修饰方法是通过 ACC_SYNCRHONIZED 来实现其语义的。

二,ACC_SYNCRHONIZED实现原理

当JVM执行引擎执行某一个方法时,其会从方法区中获取该方法的access_flags,检查其是否有ACC_SYNCRHONIZED标识符,若是有该标识符,则说明当前方法是同步方法,需要先获取当前对象的monitor,再来执行方法。

三,monitorenter/monitorexit实现原理

3.1 monitorenter

我们先看一下JVM规范是怎么定义 monitorenter 和 monitorexit 的:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

翻译过来:每一个对象都会和一个监视器 monitor 关联。监视器被占用时会被锁住,其他线程无法来获取该 monitor。当 JVM 执行某个线程的某个方法内部的 monitorenter 时,它会尝试去获取当前对象对应的 monitor 的所有权。其过程如下:

1,若 monior 的进入数为0,线程可以进入 monitor,并将 monitor 的进入数置为1。当前线程成为 monitor 的 owner(所有者);

2,若线程已拥有 monitor 的所有权,允许它重入 monitor,并递增 monitor 的进入数;

3,若其他线程已经占有 monitor 的所有权,那么当前尝试获取 monitor 的所有权的线程会被阻塞,直到 monitor 的进入数变为0,才能重新尝试获取 monitor 的所有权。

3.2 monitorexit

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻译过来:
1,能执行 monitorexit 指令的线程一定是拥有当前对象的 monitor 的所有权的线程。

2,执行 monitorexit 时会将 monitor 的进入数减1。当 monitor 的进入数减为 0 时,当前线程退出 monitor,不再拥有 monitor 的所有权,此时其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权。

四,监视器monitor

无论是 synchronized 代码块还是 synchronized 方法区,其线程安全的语义实现最终依赖一个叫 monitor 的东西,那么这个神秘的东西是什么呢?下面让我们来详细介绍一下。

每一个 java 对象都会与一个监视器 monitor 关联,我们可以把它理解成为一把锁,当一个线程想要执行一段被 synchronized 圈起来的同步方法或者代码块时,该线程得先获取到 synchronized 修饰的对象对应的 monitor。

我们的java代码里不会显示地去创造这么一个monitor对象,我们也无需创建,事实上可以这么理解:我们是通过synchronized修饰符告诉JVM需要为我们的某个对象创建关联的monitor对象。

在hotSpot虚拟机中,monitor 是由 ObjectMonitor 实现的。其源码是用 c++ 来实现的,位于hotSpot虚拟机源码 ObjectMonitor.hpp 文件中。ObjectMonitor 主要数据结构如下:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //monitor进入数
    _waiters      = 0,
    _recursions   = 0;  //线程的重入次数
    _object       = NULL;
    _owner        = NULL; //标识拥有该monitor的线程
    _WaitSet      = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //多线程竞争锁进入时的单项链表
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

owner:初始时为 NULL。当有线程占有该 monitor 时,owner 标记为该线程的唯一标识。当线程释放 monitor 时,owner 又恢复为NULL。owner 是一个临界资源,JVM 是通过 CAS 操作来保证其线程安全的。

_cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq 是一个临界资源,JVM 通过 CAS 原子指令来修改_cxq队列。修改前_cxq的旧值填入了 node 的 next 字段,_cxq 指向新值(新线程)。因此 _cxq 是一个后进先出的 stack(栈)。

_EntryList:_cxq 队列中有资格成为候选资源的线程会被移动到该队列中

_WaitSet:因为调用 wait 方法而被阻塞的线程会被放在该队列中

0

评论区