• Java多线程对象内置锁(ObjectMonitor)


    1、介绍
    Monitor是在JVM层对Java并发控制synchronized的重量级锁的实现。通过ObjectMonitor来实现并发的锁控制。同时也是Java基础对象Object的wait,nofity方法的底层支持实现。

    2、对象模型
    2.1 数据结构图
    ObjectMonitor整体上可以分为两部分,一部分是是这个监控对象的基本信息表示当前锁的实时状态,一部分表示各种情况下需要获取锁的排队信息。如下图所示:

    基本信息
    1 header

    用来保存锁对象的mark word的值。因为object里面已经不保存mark word的原来的值了,保存的是ObjectMonitor对象的地址信息。当所有线程都完成了之后,需要销毁掉ObjectMonitor的时候需要将原有的header里面的值重新复制到mark word中来。

    2 object

    指向的是对象的地址信息,方便通过ObjectMonitor来访问对应的锁对象。

    3 owner

    指向的是当前获得线程的地址,用来判断当前锁是被哪个线程持有。

    排队信息

    1cxq

    线程刚进来的时候没有获取到锁的时候,在当前的排队队列。

    2waitSet

    是指已经获取得一次锁了,对象调用了wait方法,讲当前线程挂起了就进入了等待队列。等待时间到期的时候唤醒,或者其他线程唤醒。

    3 entryList

    是队列用来获取锁的缓冲区,用来将cxq和waitSet中的数据 移动到entryList进行排队。这个统一获取锁的入口。一般是cxq 或者waitSet数据复制过来进行统一排队。

    2.2 ObjectMonitor 的源代码

    ObjectMonitor() {
    _header = NULL;
    _count = 0;
    _waiters = 0,
    _recursions = 0;
    _object = NULL;
    _owner = NULL;
    _WaitSet = NULL;
    _WaitSetLock = 0 ;
    _Responsible = NULL ;
    _succ = NULL ;
    //多线程竞争锁进入时的单向链表
    _cxq = NULL ;
    FreeNext = NULL ;
    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
    _EntryList = NULL ;
    _SpinFreq = 0 ;
    _SpinClock = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
    }

    2.3 ObjectWaiter源代码
    下面是排队对象ObjectWaiter的源代码的信息

    class ObjectWaiter : public StackObj {
    public:
    enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
    enum Sorted { PREPEND, APPEND, SORTED } ;
    ObjectWaiter * volatile _next;
    ObjectWaiter * volatile _prev;
    //等待的线程
    Thread* _thread;
    jlong _notifier_tid;
    ParkEvent * _event;
    volatile int _notified ;
    volatile TStates TState ;
    Sorted _Sorted ; // List placement disposition
    bool _active ; // Contention monitoring is enabled
    };


    3、核心方法
    3.1 TryLock

    int ObjectMonitor::TryLock (Thread * Self) {
    for (;;) {
    void * own = _owner ;
    //已经有线程获取到锁了 直接返回
    if (own != NULL){
    return 0 ;
    }
    //获取锁成功
    if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
    return 1 ;
    }
    if (true) return -1 ;
    }
    }


    通过上面代码我们可以详细的获取锁的逻辑,判断当前的监视器对象是否已经为空了,如果不为空说明已经有线程拿到锁了直接结束。当为空闲的尝试设置当前线程来获取锁,成功了直接返回1 。通过上面代码看总体上只做一次尝试,并不会多次尝试。

    3.3 try_enter
    try_enter 的方法是尝试获取当前锁对象 ,在已经拿过一次锁对象了之后,二次重新进入锁的场景使用。整个过程是只做一次尝试,不论成功失败都只执行一次。

    bool ObjectMonitor::try_enter(Thread* THREAD) {
    if (THREAD != _owner) {
    //判断当前线程是否已经活动锁
    if (THREAD->is_lock_owned ((address)_owner)) {
    _owner = THREAD ;
    _recursions = 1 ;
    OwnerIsThread = 1 ;
    return true;
    }
    //设置当前线程为当前线程
    if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
    return false;
    }
    return true;
    } else {
    _recursions++;
    return true;
    }
    }
    通过上述逻辑我们可以看到当前的线程是否已经持有了这个锁对象,如果持有了直接返回,如果没有持有尝试持有下。

    3.3 enter
    enter方法是整个获取到锁逻辑的核心实现,核心的思想是在整个链路中尽一切可能的获取到锁,如果实在获取不到情况进入等待队列进行等待。整体思路如下:

    1 尝试获取获取获取锁,获取到成功直接返回。这个时刻整个等待成不是最低的。

    2 判断自己是否已经拿到锁了,如果前面当前线程已经获取到锁了,也就直接返回。从这这里看ObjectMonitor是可以重入的。

    3 通过自旋多次尝试获取到锁的信息,如果获取到直接返回。满满的求生生欲望,避免自己加入到等待。

    4 通过自旋转调用EnterI方法让自己加入到cxq等待队列。一旦调用成功了整个线程就是park了,交出了执行状态等待唤醒了。

    5 整个获取到锁之后就调用exit推出了。

    void ATTR ObjectMonitor::enter(TRAPS) {
    //定义当前线程变量
    Thread * const Self = THREAD ;
    void * cur ;
    
    //1 如果没有人持有这个monitor 直接将自己设置成持有者
    cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
    if (cur == NULL) {
    return ;
    }
    //2 如果已经持有了 计数+1
    if (cur == Self) {
    _recursions ++ ;
    return ;
    }
    //判断当前线程的地址
    if (Self->is_lock_owned ((address)cur)) {
    _recursions = 1 ;
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
    }
    
    Self->_Stalled = intptr_t(this) ;
    //3 如果允许自旋,通过自旋转来获取锁的信息
    if (Knob_SpinEarly && TrySpin (Self) > 0) {
    assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
    Self->_Stalled = 0 ;
    return ;
    }
    //前面的尝试都失败了就开始正式的进入到等待队列进行等待了
    JavaThread * jt = (JavaThread *) Self ;
    //增加等待计数
    Atomic::inc_ptr(&_count);
    {
    //4 自旋进入到等待队列中来
    for (;;) {
    jt->set_suspend_equivalent();
    //重点是加入等待队列中来
    EnterI (THREAD) ;
    _recursions = 0 ;
    _succ = NULL ;
    //5 推出机制
    exit (false, Self) ;
    jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);
    }
    //减少等待计数
    Atomic::dec_ptr(&_count);
    Self->_Stalled = 0 ;
    }

      


    3.4 EnterI
    EnterI 是整个进入等待队列的核心实现,核心的思想是在尽最后可能的获取到锁,如果实在不行加入到_cxq的等待队列,加入队列成功后将当前线程挂起。整体思路如下:

    1 尝试通过TryLock尝试获取下对象锁的信息。

    2 尝试通过自旋锁来获取锁的信息,这也是最后一次尝试了。实在不行就只能等待了。

    3 构造等待节点对象,开始进行等待了。

    4 将当前线程的等待对象加入到能对队列_cxq排队队列中来

    5 每个执行的循环周期都会经历尝试获取锁,获取不到将自己挂起park的过程。中间有个小插曲尝试将自己设置成整个对象的维护线程(_Responsiblew),是防止获取锁的所有线程都进入了等待队列,没有人能通知其他线程进行拿锁的操作。

    void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    //1 尝试获取当前的锁对象
    if (TryLock (Self) > 0) {
    return ;
    }
    DeferredInitialize () ;
    //2 尝试通过自旋获取到锁的机制
    if (TrySpin (Self) > 0) {
    return ;
    }
    //3 构造一个 ObjectWaiter对象 也就是等待对象
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev = (ObjectWaiter *) 0xBAD ;
    node.TState = ObjectWaiter::TS_CXQ ;
    
    //4 加入到能对队列_cxq中来
    ObjectWaiter * nxt ;
    for (;;) {
    node._next = nxt = _cxq ;
    //添加完成直接返回
    if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
    //顺便捎带加入下锁 看看是否成功
    if (TryLock (Self) > 0) {
    return ;
    }
    }
    
    //如果没有其他线程 将当前线程设置成负责线程 以方便后面对象回收
    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
    Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }
    
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;
    //5 循环进入等待和唤醒的情况,这个代码需要仔细研读
    for (;;) {
    //尝试加个锁看看是否有运气
    if (TryLock (Self) > 0) break ;
    if ((SyncFlags & 2) && _Responsible == NULL) {
    Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }
    //防止所有的线程都进入死等的情况,这样就会出现线程死锁
    if (_Responsible == Self || (SyncFlags & 1)) {
    //过一段时间就起来看看
    Self->_ParkEvent->park ((jlong) RecheckInterval) ;
    RecheckInterval *= 8 ;
    if (RecheckInterval > 1000) RecheckInterval = 1000 ;
    } else {
    Self->_ParkEvent->park() ;
    }
    //尝试加个锁看看是否有运气
    if (TryLock(Self) > 0) break ;
    ++ nWakeups ;
    // 自旋试下 是否成功
    if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
    if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
    Self->_ParkEvent->reset() ;
    OrderAccess::fence() ;
    }
    if (_succ == Self) _succ = NULL ;
    }
    //已经获取到了锁,讲当前节点从等待队列中解除
    UnlinkAfterAcquire (Self, &node) ;
    return ;
    }


    3.5 exit
    线程执行同步方法或代码块结束之后,会调用exit方法将当前线程从锁对象中移除,并尝试唤醒启动的线程来获取锁的过程。核心要点有两个,一个是将当前线程释放,这个很好理解就把_owner字段中线程值设为空就好了。另一个是唤醒其他线程,这个就涉及到相关的策略,因为前面他有一个排队队列_cxq,还有一个_EntryList,到底从哪里优先去取。提供了四种策略:

    QMode=2 的时候 优先从_cxq唤醒,执行过程如下图:

    QMode=3 的时候 _cxq中的数据加入到_EntryList尾部中来 然后从_EntryList开始获取

    QMode=4的时候 _cxq中的数据加入到_EntryList前面来 然后从_EntryList开始获取

    QMode=1 的时候 这个是默认策略 优先从_EntryList 中获取 如果_EntryList为空的情况,把_cxq进行队列反转然后从_cxq获取

    整个策略本质上是考虑公平性与吞吐效率的考量。

    void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
    Thread * Self = THREAD ;
    //1判断当前线程是否是锁的线程
    if (THREAD != _owner) {
    if (THREAD->is_lock_owned((address) _owner)) {
    _owner = THREAD ;
    _recursions = 0 ;
    OwnerIsThread = 1 ;
    }
    }
    //重入锁的次数减1
    if (_recursions != 0) {
    _recursions--; // this is simple recursive enter
    return ;
    }
    
    // Invariant: after setting Responsible=null an thread must execute
    // a MEMBAR or other serializing instruction before fetching EntryList|cxq.
    if ((SyncFlags & 4) == 0) {
    _Responsible = NULL ;
    }
    for (;;) {
    
    if (Knob_ExitPolicy == 0) {
    OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
    OrderAccess::storeload() ;
    //没有人需要获取锁了直接返回
    if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
    return ;
    }
    //当锁已经被其他线程抢占了 直接推出就好了
    if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
    return ;
    }
    } else {
    if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
    OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
    OrderAccess::storeload() ;
    if (_cxq == NULL || _succ != NULL) {
    return ;
    }
    if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
    return ;
    }
    }
    }
    
    ObjectWaiter * w = NULL ;
    int QMode = Knob_QMode ;
    
    //当QMode=2的时候 优先从_cxq唤醒
    if (QMode == 2 && _cxq != NULL) {
    w = _cxq ;
    assert (w != NULL, "invariant") ;
    assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
    ExitEpilog (Self, w) ;
    return ;
    }
    //当QMode=3的时候 讲_cxq中的数据加入到_EntryList尾部中来 然后从_EntryList开始获取
    if (QMode == 3 && _cxq != NULL) {
    w = _cxq ;
    for (;;) {
    ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
    if (u == w) break ;
    w = u ;
    }
    assert (w != NULL , "invariant") ;
    
    ObjectWaiter * q = NULL ;
    ObjectWaiter * p ;
    for (p = w ; p != NULL ; p = p->_next) {
    guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
    p->TState = ObjectWaiter::TS_ENTER ;
    p->_prev = q ;
    q = p ;
    }
    
    //讲_cxq数据加入到_EntryList的队列中来
    ObjectWaiter * Tail ;
    for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
    if (Tail == NULL) {
    _EntryList = w ;
    } else {
    Tail->_next = w ;
    w->_prev = Tail ;
    }
    
    
    }
    //当QMode=4的时候 讲_cxq中的数据加入到_EntryList前面来 然后从_EntryList开始获取
    if (QMode == 4 && _cxq != NULL) {
    w = _cxq ;
    for (;;) {
    assert (w != NULL, "Invariant") ;
    ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
    if (u == w) break ;
    w = u ;
    }
    
    //批量修改状态标志改成TS_ENTER
    ObjectWaiter * q = NULL ;
    ObjectWaiter * p ;
    for (p = w ; p != NULL ; p = p->_next) {
    p->TState = ObjectWaiter::TS_ENTER ;
    p->_prev = q ;
    q = p ;
    }
    
    //插到原有的_EntryList前面 从员_EntryList中获取
    if (_EntryList != NULL) {
    q->_next = _EntryList ;
    _EntryList->_prev = q ;
    }
    _EntryList = w ;
    
    // Fall thru into code that tries to wake a successor from EntryList
    }
    
    w = _EntryList ;
    if (w != NULL) {
    assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
    ExitEpilog (Self, w) ;
    return ;
    }
    
    // If we find that both _cxq and EntryList are null then just
    // re-run the exit protocol from the top.
    w = _cxq ;
    if (w == NULL) continue ;
    
    //默认的策略
    for (;;) {
    assert (w != NULL, "Invariant") ;
    ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
    if (u == w) break ;
    w = u ;
    }
    //将cxq排队队列进行翻转。
    if (QMode == 1) {
    // QMode == 1 : drain cxq to EntryList, reversing order
    // We also reverse the order of the list.
    ObjectWaiter * s = NULL ;
    ObjectWaiter * t = w ;
    ObjectWaiter * u = NULL ;
    while (t != NULL) {
    guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
    t->TState = ObjectWaiter::TS_ENTER ;
    u = t->_next ;
    t->_prev = u ;
    t->_next = s ;
    s = t;
    t = u ;
    }
    _EntryList = s ;
    assert (s != NULL, "invariant") ;
    } else {
    // QMode == 0 or QMode == 2
    _EntryList = w ;
    ObjectWaiter * q = NULL ;
    ObjectWaiter * p ;
    for (p = w ; p != NULL ; p = p->_next) {
    guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
    p->TState = ObjectWaiter::TS_ENTER ;
    p->_prev = q ;
    q = p ;
    }
    }
    if (_succ != NULL) continue;
    
    w = _EntryList ;
    if (w != NULL) {
    guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
    ExitEpilog (Self, w) ;
    return ;
    }
    }
    }

    在HotSpot虚拟机中,Monitor是基于C++的ObjectMonitor类实现的,其主要成员包括:

    • _owner:指向持有ObjectMonitor对象的线程
    • _WaitSet:存放处于wait状态的线程队列,即调用wait()方法的线程
    • _EntryList:存放处于等待锁block状态的线程队列
    • _count:约为_WaitSet 和 _EntryList 的节点数之和
    • _cxq: 多个线程争抢锁,会先存入这个单向链表
    • _recursions: 记录重入次数

    在这里插入图片描述

    上图简略展示了ObjectMonitor的基本工作机制:

    (1)当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中。

    (2)当某个线程获取到对象的Monitor后进入临界区域,并把Monitor中的 _owner 变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。

    (3)若持有Monitor的线程调用 wait() 方法,将释放当前持有的Monitor,_owner变量恢复为null,_count自减1,同时该线程进入 _WaitSet 集合中等待被唤醒。

    (4)在_WaitSet 集合中的线程会被再次放到_EntryList 队列中,重新竞争获取锁。

    (5)若当前线程执行完毕也将释放Monitor并复位变量的值,以便其他线程进入获取锁。

    线程争抢锁的过程要比上面展示得更加复杂。除了_EntryList 这个双向链表用来保存竞争的线程,ObjectMonitor中还有另外一个单向链表 _cxq,由两个队列来共同管理并发的线程。

    在这里插入图片描述
    ObjectMonitor::enter() 和 ObjectMonitor::exit() 分别是ObjectMonitor获取锁和释放锁的方法。线程解锁后还会唤醒之前等待的线程,根据策略选择直接唤醒_cxq队列中的头部线程去竞争,或者将_cxq队列中的线程加入_EntryList,然后再唤醒_EntryList队列中的线程去竞争。

    ObjectMonitor::enter()
    在这里插入图片描述
    下面我们看一下ObjectMonitor::enter()方法竞争锁的流程:

    首先尝试通过 CAS 把 ObjectMonitor 中的 _owner 设置为当前线程,设置成功就表示获取锁成功。通过 _recursions 的自增来表示重入。

    如果没有CAS成功,那么就开始启动自适应自旋,自旋还不行的话,就包装成 ObjectWaiter 对象加入到 _cxq 单向链表之中。关于自旋锁和自适应自旋,可以参考前文《Java面试必考问题:什么是自旋锁 》](https://www.toutiao.com/i6934327407897854475/?group_id=6934327407897854475)。

    加入_cxq链表后,再次尝试是否可以CAS拿到锁,再次失败就要阻塞(block),底层调用了pthread_mutex_lock。

    ObjectMonitor::exit()方法

    线程执行 Object.wait()方法时,会将当前线程加入到 _waitSet 这个双向链表中,然后再运行ObjectMonitor::exit() 方法来释放锁。

    可重入锁就是根据 _recursions 来判断的,重入一次就执行 _recursions++,解锁一次就执行 _recursions–,如果 _recursions 减到 0 ,就说明需要释放锁了。

    线程解锁后还会唤醒之前等待的线程。当线程执行 Object.notify()方法时,从 _waitSet 头部拿线程节点,然后根据策略(QMode指定)决定将线程节点放在哪里,包括_cxq 或 _EntryList 的头部或者尾部,然后唤醒队列中的线程。

    在这里插入图片描述

  • 相关阅读:
    javascript的语法作用域你真的懂了吗
    网页的三种布局(固定宽度式,流体式,弹性式)
    css3系列之animation
    跟我学习css3之transition
    函数调用你知道几种方法
    javascript的那些事儿你都懂了吗
    css3的那些高级选择器二
    [转]影响Cache的几个HTTP头信息
    CSS属性合写
    defer 与 async
  • 原文地址:https://www.cnblogs.com/myf008/p/16396915.html
Copyright © 2020-2023  润新知