• 对象内置锁ObjectMonitor


    内置锁(ObjectMonitor)

    Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。

    通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象,该对象是在HotSpot底层C++语言编写的(openjdk里面看),简单看一下代码:

    //结构体如下
    ObjectMonitor::ObjectMonitor() {  
      _header       = NULL;  
      _count       = 0;  
      _waiters      = 0,  
      _recursions   = 0;       //线程的重入次数
      _object       = NULL;  
      _owner        = NULL;    //标识拥有该monitor的线程
      _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
      _WaitSetLock  = 0 ;  
      _Responsible  = NULL ;  
      _succ         = NULL ;  
      _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
      FreeNext      = NULL ;  
      _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
      _SpinFreq     = 0 ;  
      _SpinClock    = 0 ;  
      OwnerIsThread = 0 ;  
    }  
    

    特别重要的两个属性:

    监控区(Entry Set):锁已被其他线程获取,期待获取锁的线程就进入Monitor对象的监控区

    待授权区(Wait Set):曾经获取到锁,但是调用了wait方法,线程进入待授权区

    ObjectMonitor队列之间的关系转换:

    img

    内置锁状态转换图:

    对象内置锁ObjectMonitor流程

    • 所有期待获得锁的线程,在锁已经被其它线程拥有的时候,这些期待获得锁的线程就进入了对象锁的entry set区域。
    • 所有曾经获得过锁,但是由于其它必要条件不满足而需要wait的时候,线程就进入了对象锁的wait set区域 。
    • wait set区域的线程获得Notify/notifyAll通知的时候,随机的一个Thread(Notify)或者是全部的Thread(NotifyALL)从对象锁的wait set区域进入了entry set中。
    • 在当前拥有锁的线程释放掉锁的时候,处于该对象锁的entryset区域的线程都会抢占该锁,但是只能有任意的一个Thread能取得该锁,而其他线程依然在entry set中等待下次来抢占到锁之后再执行。

    既然提到了_waitSet_EntryList(_cxq队列后面会说),那就看一下底层的waitnotify方法

    wait方法的实现过程:

      //1.调用ObjectSynchronizer::wait方法
    void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
      /*省略 */
      //2.获得Object的monitor对象(即内置锁)
      ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
      DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
      //3.调用monitor的wait方法
      monitor->wait(millis, true, THREAD);
      /*省略*/
    }
      //4.在wait方法中调用addWaiter方法
      inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
      /*省略*/
      if (_WaitSet == NULL) {
        //_WaitSet为null,就初始化_waitSet
        _WaitSet = node;
        node->_prev = node;
        node->_next = node;
      } else {
        //否则就尾插
        ObjectWaiter* head = _WaitSet ;
        ObjectWaiter* tail = head->_prev;
        assert(tail->_next == head, "invariant check");
        tail->_next = node;
        head->_prev = node;
        node->_next = head;
        node->_prev = tail;
      }
    }
      //5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park  也就是wait
    

    总结:通过object获得内置锁(objectMonitor),通过内置锁将Thread封装成OjectWaiter对象,然后addWaiter将它插入以_waitSet为首结点的等待线程链表中去,最后释放锁。

    notify方法的底层实现

      //1.调用ObjectSynchronizer::notify方法
        void ObjectSynchronizer::notify(Handle obj, TRAPS) {
        /*省略*/
        //2.调用ObjectSynchronizer::inflate方法
        ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
    }
        //3.通过inflate方法得到ObjectMonitor对象
        ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
        /*省略*/
         if (mark->has_monitor()) {
              ObjectMonitor * inf = mark->monitor() ;
              assert (inf->header()->is_neutral(), "invariant");
              assert (inf->object() == object, "invariant") ;
              assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
              return inf 
          }
        /*省略*/ 
          }
        //4.调用ObjectMonitor的notify方法
        void ObjectMonitor::notify(TRAPS) {
        /*省略*/
        //5.调用DequeueWaiter方法移出_waiterSet第一个结点
        ObjectWaiter * iterator = DequeueWaiter() ;
        //6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
        /**省略*/
      }
    

    总结:通过object获得内置锁(objectMonitor),调用内置锁的notify方法,通过_waitset结点移出等待链表中的首结点,将它置于_EntrySet中去,等待获取锁。注意:notifyAll根据policy不同可能移入_EntryList或者_cxq队列中,此处不详谈。

    总结:

    每个对象的Object Monitor控制过程相对独立,但是一个线程可以同时拥有一个或者多个对象的操作权限。

    参考:

    Java线程同步与信号量的奥秘

    JVM 管程介绍

    Java并发基石——所谓“阻塞”:Object Monitor和AQS(1)

  • 相关阅读:
    图片热点 网页划区 网页的拼接 表单
    html body的属性 格式控制标签 内容容器标签 超链接标签 图片标签 表格
    结构体
    out 传值
    c#数组,手机号随机数抽奖
    输入月份和日期,输出是今年的第多少天,利用switch和case
    c#,for穷举,百鸡百钱
    c#条件运算符的使用,判断时间是上午还是下午
    c#关于try catch finally的使用,判断日期格式是否正确
    c#数组,例题
  • 原文地址:https://www.cnblogs.com/hongdada/p/14513036.html
Copyright © 2020-2023  润新知