Synchronized的底层实现原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zm357561003/article/details/102717397

Synchronized的底层实现原理

如果我们要想了解synchronized底层实现原理,那么不妨先来了解了解对象在堆中的存储结构
java对象在堆中的存储结构分为三个部分,分别为对象头、实例变量和填充字节。

  • 对象头里面主要包含MarkWord和Klass Point类型指针,MarkWord里面存储的是对象自身的运行时数据(包含了Hashcode、GC分带年龄、锁状态标记、线程持有的锁等等),而Klass Point则指向的是类元数据的指针,JVM就是通过此指针来确定这个对象是哪个类的实例。
  • 实例变量里面存储的是对象的属性信息,包括父类的属性信息,按照4个字节对齐
  • 填充字节则是因为java虚拟机要求对象字节数必须是8的整数倍,填充字节就是用于凑齐这个倍数用的。
    在JVM中,synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现的ObjectMonitor对象)的起始地址。它的大致数据结构如下:
    ObjectMonitor() {
        _count        = 0; //用来记录该对象被线程获取锁的次数
        _waiters      = 0;
        _recursions   = 0; //锁的重入次数
        _owner        = NULL; //指向持有ObjectMonitor对象的线程 
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
      }
    

我们知道线程的生命周期分为5个状态,分别为start,runing,waiting、blocking、dead
当synchronized修饰方法和代码块时,流程如下:

  • 当多个线程同时访问该方法时候,这些线程会先进入_EntryList队列,此时这些线程处于blocking状态
  • 当某一个线程获取到了实例对象的监视器(monitor)锁,那么就进入running状态,执行线程的方法,此时ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象被一个线程获取
  • 当running状态的线程调用wait()方法的时候,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner置为null,_count减1,同时该线程进入_WaitSet队列,直到有线程调用notify或者notifyAll方法唤醒该线程,则该线程重新获取到monitor对象进入running状态
  • 如果当前线程执行完毕,那么也释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner置为null,_count减1
    那么synchronized又是如何获得monitor对象呢?
  • synchronized如果修饰的是代码块,则是在代码块开始的位置插入monitorenter命令,在同步结束的位置或者异常出现的位置插入monitorexit指令;JVM要保证monitorenter和monitorexit成对出现,故monitorexit出现两次,防止异常导致无法配对,我们查看字节码会发现其代码如下:
    在这里插入图片描述
  • synchronized如果修饰的是方法,则不是通过插入monitorenter和monitorexit指令实现的,而是通过设置方法表结构中的ACC_SYNCHRONIZED标识,方法在执行时,会读取这个标识,如果发现有此标识则会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕则释放monitor对象,所谓的获取monitor对象和释放monitor对象指的就是操作monitor对象的_owner和_count属性,我们查看字节码会发现其代码如下:
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zm357561003/article/details/102717397