JDK18:Synchronizer的cpp源码分析(二)——重量级锁竞争&重入&自旋&中止策略&奖罚&cxq

114 阅读8分钟

1、enter()——重量级锁的竞争和锁重入处理

bool ObjectMonitor::enter(JavaThread* current) {
    // 刚好没有线程持有锁的情况,尝试对锁进行非公平性的竞争,内部是个CAS
    // 尝试将锁持有者从NULL设置为当前访问线程
    // 方法返回值为owner旧值,也就是说如果返回NULL代表无人持有锁,并且CAS成功了
  void* cur = try_set_owner_from(NULL, current);
  if (cur == NULL) {
    // 如果成功将锁的持有者设置为当前线程,则返回 true
    assert(_recursions == 0, "invariant");
    return true;
  }

  //竞争锁失败,判断持锁线程是不是就是当前线程
  if (cur == current) {
      //如果是的话,走重入锁逻辑,锁计数加一并且返回true
    // TODO-FIXME: check for integer overflow!  BUGID 6557169.  哈哈
    _recursions++;
    return true;
  }

  // 锁是否由轻量级锁升级而来?
  // 判断锁是轻量级锁的时候是否被当前线程持有过,现在当前线程尝试重新获取锁
  if (current->is_lock_owned((address)cur)) {
    assert(_recursions == 0, "internal state error");
    _recursions = 1; //计数为1
    //将当前线程标记为锁的持有者,并更新相应的线程与锁之间的关联关系。
    set_owner_from_BasicLock(cur, current);  // Convert from BasicLock* to Thread*.
    return true;
  }

  assert(current->_Stalled == 0, "invariant");
  current->_Stalled = intptr_t(this);

  // 进入自旋流程
  if (TrySpin(current) > 0) {
    assert(owner_raw() == current, "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
    assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
    assert(object()->mark() == markWord::encode(this),
           "object mark must match encoded this: mark=" INTPTR_FORMAT
           ", encoded this=" INTPTR_FORMAT, object()->mark().value(),
           markWord::encode(this).value());
    current->_Stalled = 0;
    return true;
  }
  //代码未完,先省略 看trySpin

2、自旋——TrySpin()

int ObjectMonitor::TrySpin(JavaThread* current) {
  // 固定自旋次数 默认是0,jdk1.6后不能通过jvm参数调整次数了
  int ctr = Knob_FixedSpin;
  if (ctr != 0) {
    while (--ctr >= 0) { //每次自旋 自旋次数-1
      //自旋
      if (TryLock(current) > 0) return 1; //尝试获取锁 cas Monitor owner
      SpinPause(); //暂停一会儿,再进入下次自旋
    }
    //规定次数内没自旋成功直接return 0
    return 0;
  }
  
  //自适应自旋 Knob_PreSpin = 10  所以此阶段共自旋11次
  for (ctr = Knob_PreSpin + 1; --ctr >= 0;) {
      //尝试获取锁 cas Monitor owner
      //TryLock()成功后会返回1,失败返回-1
    if (TryLock(current) > 0) {
      // 到这里就代表自旋成功了,开始自旋奖励机制,奖励的是下次的可自旋次数
      int x = _SpinDuration;
      // x < 5000 5000就是最大自旋次数
      if (x < Knob_SpinLimit) { 
        // x < 1000 则 x= 1000,1000是一个底线,小于此数会将可自旋次数直接奖励到1000
        if (x < Knob_Poverty) x = Knob_Poverty; 
        // x >= 1000时,可自旋次数奖励100,Knob_BonusB = 100
        _SpinDuration = x + Knob_BonusB; //注意这个_SpinDuration,第一次应该是1000 + 100
      }
      return 1;
    }
    //每次自旋失败暂停一下下,用的汇编的pause指令
    SpinPause();
  }
  
  // 拿到自旋次数 本例为1100
  // 注意,这个1100是上一次自旋成功且这次11次自旋失败才有的
  ctr = _SpinDuration;  
  //如果没有自旋次数 直接返回,自旋失败
  if (ctr <= 0) return 0;

  if (NotRunnable(current, (JavaThread*) owner_raw())) { 
    return 0;
  }

  //这个succ表示当前线程是否预计是下一个锁的拥有者
  //如果succ没有记录线程,则记录当前线程作为预计下一个锁的拥有者
  //succ主要用于中止策略中的处理,往下看吧
  if (_succ == NULL) {
    _succ = current;
  }
  Thread* prv = NULL;

  //开始完全自旋,次数很大
  while (--ctr >= 0) {
      
    //检查循环计数 ctr 的低8位是否为0
    if ((ctr & 0xFF) == 0) {
        //判断当前线程是否应该处理安全点。
        //安全点不懂看jvm
      if (SafepointMechanism::should_process(current)) {
        goto Abort;           // abrupt spin egress
      }
      SpinPause();
    }

    // 将锁的拥有者转换为Java线程对象类型
    JavaThread* ox = (JavaThread*) owner_raw();
    if (ox == NULL) { // 如果当前无线程持有锁
      // 尝试获取,尝试CAS锁持有者为当前线程
      // 返回值是旧值,如果返回NULL就代表成功了
      ox = (JavaThread*)try_set_owner_from(NULL, current);
      if (ox == NULL) {
        // 到这里就获取成功了
        // 是预计的线程获得了锁的话,更新succ为NULL
        if (_succ == current) {
          _succ = NULL;
        }

        //进入奖罚机制 本例此时x = 1100
        int x = _SpinDuration;
        if (x < Knob_SpinLimit) {  // x < 5000
          if (x < Knob_Poverty) x = Knob_Poverty;   //若x < 1000 则 x =1000
          _SpinDuration = x + Knob_Bonus; //奖100 可自旋次数变成1200了
        }
        return 1; //返回成功
      }

      // 到这里就是竞争失败了,说明当一个锁处于无主状态时,多个线程尝试获取,本线程没竞争到,没有必要再自旋尝试了
      // ox就是竞争成功的那个线程,记录下来
      prv = ox; 
      goto Abort; //进入中止流程
    }
    
    // 当前锁拥有者与上一次锁拥有者不同,并且上一个拥有者不为空
    // 这说明锁已经经历了一次释放,又被别的线程竞争走了,这时没必要继续自旋等待了
    if (ox != prv && prv != NULL) {
      //进入中止
      goto Abort;
    }
    prv = ox;

    //线程挂了,也是进入中止了
    if (NotRunnable(current, ox)) {
      goto Abort;
    }
    
    if (_succ == NULL) {
      _succ = current;
    }
  }

  // 自旋失败的惩罚机制
  {
    int x = _SpinDuration;
    if (x > 0) {
      x -= Knob_Penalty;  //罚200次数
      if (x < 0) x = 0; // 如果次数已经小于0则赋值0
      _SpinDuration = x;
    }
  }

//中断处理
Abort:
  // 检查当前线程是否为下一个预计的拥有者
  // 这是中止自旋前的最后一次尝试
  if (_succ == current) {
    _succ = NULL;
    // storeload屏障 万能屏障 加lock前缀并且刷缓存的那个
    // 这是为了确保别的线程对_succ = NULL可见
    OrderAccess::fence(); 
    if (TryLock(current) > 0) return 1; //尝试获取锁
  }
  // 如果不是预计的下一个拥有者,直接return 0,自旋失败
  return 0;
}

3、自旋之后

  • 我们接着enter()的代码继续往下
  // 进入自旋流程
  if (TrySpin(current) > 0) {
    // 断言当前持有者必须是当前线程
    assert(owner_raw() == current, "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
    // 断言锁计数归0
    assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
    assert(object()->mark() == markWord::encode(this),
           "object mark must match encoded this: mark=" INTPTR_FORMAT
           ", encoded this=" INTPTR_FORMAT, object()->mark().value(),
           markWord::encode(this).value());
    current->_Stalled = 0; // 重置自旋标志,表示自旋结束
    return true; // 成功
  }
  
  // 作者中间删除了一些关系不是很大的代码,直接看enterI()
  // 到这里就是自旋失败了
 
    for (;;) {
    // 创建一个 ExitOnSuspend 对象,用于监视当前 ObjectMonitor 的挂起状态 也不用管
      ExitOnSuspend eos(this);
      {
        ThreadBlockInVMPreprocess<ExitOnSuspend> tbivs(current, eos, true /* allow_suspend */);
        
        //看这里
        EnterI(current);
        
        current->set_current_pending_monitor(NULL);
      }
      if (!eos.exited()) {
        // ExitOnSuspend did not exit the OM
        assert(owner_raw() == current, "invariant");
        break;
      }
    }
  }

  add_to_contentions(-1);
  assert(contentions() >= 0, "must not be negative: contentions=%d", contentions());
  current->_Stalled = 0;

  // Must either set _recursions = 0 or ASSERT _recursions == 0.
  assert(_recursions == 0, "invariant");
  assert(owner_raw() == current, "invariant");
  assert(_succ != current, "invariant");
  assert(object()->mark() == markWord::encode(this), "invariant");


  DTRACE_MONITOR_PROBE(contended__entered, this, object(), current);
  if (JvmtiExport::should_post_monitor_contended_entered()) {
    JvmtiExport::post_monitor_contended_entered(current, this);

  }
  if (event.should_commit()) {
    event.set_previousOwner(_previous_owner_tid);
    event.commit();
  }
  OM_PERFDATA_OP(ContendedLockAttempts, inc());
  return true;
}

4、enterI() —— 再自旋与cxq

  • 先简单说一下park,直接看代码吧,应该很容易理解
public static void main(String[] args) {
    Thread main = Thread.currentThread();
    LockSupport.unpark(main); // 加油 x + 1
    LockSupport.park(); // 消耗油 x - 1 
    System.out.println("暂停了吗?"); // 可输出
}

public static void main(String[] args) {
    Thread main = Thread.currentThread();
    LockSupport.unpark(main); // 加油 x + 1
    LockSupport.park(); // 消耗油 x - 1
    LockSupport.park(); // 消耗油 x - 1  x = -1
    System.out.println("暂停了吗?"); // 不输出
}
void ObjectMonitor::EnterI(JavaThread* current) {
  assert(current->thread_state() == _thread_blocked, "invariant");

  // 再自旋一次
  if (TryLock (current) > 0) {
    assert(_succ != current, "invariant");
    assert(owner_raw() == current, "invariant");
    assert(_Responsible != current, "invariant");
    return;
  }

  // 说白了也是自旋,再自旋一次
  if (try_set_owner_from(DEFLATER_MARKER, current) == DEFLATER_MARKER) {
    add_to_contentions(1);
    assert(_succ != current, "invariant");
    assert(_Responsible != current, "invariant");
    return;
  }

  assert(InitDone, "Unexpectedly not initialized");

  // 又调用了TrySpin(),好想吐槽
  if (TrySpin(current) > 0) {
    assert(owner_raw() == current, "invariant");
    assert(_succ != current, "invariant");
    assert(_Responsible != current, "invariant");
    //到这就是自旋成功了
    return;
  }

  assert(_succ != current, "invariant");
  assert(owner_raw() != current, "invariant");
  assert(_Responsible != current, "invariant");

  // 到这里他才终于放弃挣扎?准备加入cxq
  // 简单介绍一下cxq,cxq是一个FIFO(先进先出)的等待队列,专门存放这些自旋失败的线程
  // 他的内部是一个单向链表,存取策略是新线程插入链表头,唤醒线程时从链表尾取
  // 创建一个ObjectWaiter类型的节点 node,节点中存放的是当前线程
  ObjectWaiter node(current);
  // 重置了当前的park
  current->_ParkEvent->reset();
  node._prev   = (ObjectWaiter*) 0xBAD;
  // 设置节点状态node.Ts=State  表示该节点处于等待队列(CXQ)状态
  node.TState  = ObjectWaiter::TS_CXQ;

  ObjectWaiter* nxt;
  for (;;) {
    // node._next 和 nxt指向 cxq 头节点
    node._next = nxt = _cxq;
    // 这其实就是Java中CAS的底层命令
    // 尝试将cxq头节点设置为node
    if (Atomic::cmpxchg(&_cxq, nxt, &node) == nxt) 
        //操作成功,代表成功加入cxq,退出循环
        break;

    // Interference - the CAS failed because _cxq changed.  Just retry.
    // As an optional optimization we retry the lock.
    // 操作失败,代表同时有其他线程在操作cxq
    // 为了避免竞争,再尝试获取锁一次,尝试失败则继续尝试进入cxq
    if (TryLock (current) > 0) {
      assert(_succ != current, "invariant");
      assert(owner_raw() == current, "invariant");
      assert(_Responsible != current, "invariant");
      return;
    }
  }

  if (nxt == NULL && _EntryList == NULL) {
    Atomic::replace_if_null(&_Responsible, current);
  }
  int nWakeups = 0;
  int recheckInterval = 1;

  for (;;) {

      // 又尝试获取锁
    if (TryLock(current) > 0) break;
    assert(owner_raw() != current, "invariant");

    // park self
    // 挂起线程,终于放弃挣扎了
    if (_Responsible == current) {
      current->_ParkEvent->park((jlong) recheckInterval);
      // Increase the recheckInterval, but clamp the value.
      recheckInterval *= 8;
      if (recheckInterval > MAX_RECHECK_INTERVAL) {
        recheckInterval = MAX_RECHECK_INTERVAL;
      }
    } else {
      current->_ParkEvent->park();
    }

    // 被唤醒后继续尝试获取
    if (TryLock(current) > 0) break;

    if (try_set_owner_from(DEFLATER_MARKER, current) == DEFLATER_MARKER) {
      add_to_contentions(1);
      break;
    }

    OM_PERFDATA_OP(FutileWakeups, inc());
    ++nWakeups;

    // 大自旋,尝试获取锁
    if (TrySpin(current) > 0) break;

    if (_succ == current) _succ = NULL;
    OrderAccess::fence();
  }

  assert(owner_raw() == current, "invariant");

  UnlinkAfterAcquire(current, &node);
  if (_succ == current) _succ = NULL;

  assert(_succ != current, "invariant");
  if (_Responsible == current) {
    _Responsible = NULL;
    OrderAccess::fence(); // Dekker pivot-point
  }
  return;
}
  • 笔者已经越看越无语了,ReentrantLock真香.......不过到这里,经历了无数次自旋失败再重试,线程终于进入cxq后老老实实的park了