Synchronized底层语义以及基本原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wueryan/article/details/86585056

Java对象头与Monitor

synchronized的锁对象存储在Java对象头里。在jvm的对象头里,主要是由Mark Word以及Class Metadata Address组成。
Class Metadata Address是类型指针指向对象的类元数据。JVM通过这个指针确定该对象在哪个类的实例。
Mark Word存储对象的HashCode、分代年龄、锁标记位等信息。随着对象锁的不同,具体存储的内容并不相同。具体见下表:

图暂略

当synchronized进入到重量级锁的时候,此时指针将会指向monitor对象(监视器锁)的起始地址。
在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的。数据结构如下:

ObjectMonitor() {
    _header       = NULL;//markOop对象头
    _count        = 0;
    _waiters      = 0,//等待线程数
    _recursions   = 0;//重入次数
    _object       = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
    _owner        = NULL;//指向获得ObjectMonitor对象的线程或基础锁
    _WaitSet      = NULL;//处于wait状态的线程,会被加入到wait set;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
    _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
  }

在这里插入图片描述
结合上述的数据结构以及示意图,我们可以了解到线程状态以及状态转换。

(1)_EntryList:当多个线程访问同一段同步代码,进入_EntryList集合;
(2)线程彼此竞争对象的monitor后进入_Owner区域,owner变量设置为当前线程,同时计数器count+1;
(3)线程如果调用wait(),此时释放当前持有的monitor,owner变量恢复为null.同时该线程进入WaitSet集合中等待被唤醒。
(4)线程执行完毕,则释放monitor并复位变量的值,以便其他线程进入获取monitor.

synchronized代码块

同步语句块的实现是monitorenter指令和monitorexit指令。
线程会去获取对象锁(objectref)所对应的monitor的持有权,此时计数器本身如果为0,则获取到monitor,同时将计数器设置为1。这里如果是同一个线程,那么对于可重入锁来说,计数器会加1进行累计。如果其他线程想要去获取该锁,则此时会被阻塞,直到当前线程被执行完,此时monitorexit指令被执行。

synchronized方法

方法级的同步不同于代码块,对于方法级的同步,JVM在方法表结构中会有个标识,ACC_SYNCHRONIZED访问标志区来分一个方法是否同步方法。
如果被设置了,则执行线程则现持有monitor,然后执行方法,最后方法完成,则释放monitor。
如果遇到异常,则同步方法所持有的monitor将在异常抛到同步方法外自动释放。

猜你喜欢

转载自blog.csdn.net/wueryan/article/details/86585056