In this article, Synchronized low-level implementation of the third article, the content of lightweight lock implementation.

Lightweight locks are not complicated, and many of these are covered in biased locks, which may overlap with this article.

In addition, the background and basic flow of lightweight lock has been explained in the introduction. It is strongly recommended that you read this article on the basis of two previous articles.

This series of articles will conduct a comprehensive analysis of HotSpot synchronized lock implementation, including partial lock, lightweight lock, heavyweight lock lock, lock unlock, lock upgrade process principle and source analysis, hoping to give some help to students in the study of synchronized road. It mainly includes the following articles:

Synchronized low-level implementation — An introduction

Synchronized low-level implementation — biased locking

Synchronized low-level implementation — lightweight locking

Synchronized low-level implementation — heavyweight locking

More articles can be found on my blog: github.com/farmerjohng…

This paper is divided into two parts:

1. Lightweight lock acquisition process

2. Lightweight lock release process

The JVM version I see is JDK8u, the specific version number and code can be seen here.

Lightweight lock acquisition process

Let’s begin the lightweight lock acquisition process analysis in bytecodeInterpreter. Cpp# 1816.

CASE(_monitorenter): {
  oop lockee = STACK_OBJECT(- 1); .if(entry ! =NULL) {.../ / omitted in the code above if the CAS fails will call to InterpreterRuntime: : monitorenter

    // traditional lightweight locking
    if(! success) {// Build a product of bivariate without locking
      markOop displaced = lockee->mark()->set_unlocked();
      // Set it to Lock Record
      entry->lock()->set_displaced_header(displaced);
      bool call_vm = UseHeavyMonitors;
      if(call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) ! = displaced) {// If the CAS replacement fails, the lock object is not locked
        // Is it simple recursive case?
        if(! call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) { entry->lock()->set_displaced_header(NULL);
        } else {
          // Call Monitorenter if CAS fails
          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
        }
      }
    }
    UPDATE_PC_AND_TOS_AND_CONTINUE(1.- 1);
  } else {
    istate->set_msg(more_monitors);
    UPDATE_PC_AND_RETURN(0); // Re-execute}}Copy the code

Success is false if the lock object is not in biased mode or has been biased to another thread. In this case, a mark Word in the unlocked state will be constructed and set into the Lock Record. We call the field storing the object mark Word in the Lock Record as “product Mark Word”.

If the current lock is not locked, the CAS fails. If this is a Lock reentrant, set the Product of the Lock Record to null.

Let’s look at a demo, and in the demo, we’ll repeat this 3 times to get the lock,

synchronized(obj){
    synchronized(obj){
    	synchronized(obj){
    	}
    }
}
Copy the code

Assuming that the Lock state is lightweight, the following figure reflects the state of the Mark Word and the Lock Record in the thread stack. It can be seen that the thread stack on the right contains three Lock records pointing to the current Lock object. Where the highest bit of the stack Lock Record is allocated when the Lock is acquired for the first time. The value of herbier’s herbier Mark Word is the Mark word before the function is added to the Lock object, and the Lock reentry will allocate a Lock Record of herbier’s herbier Mark Word as null in the thread stack.

Why does the JVM choose to add bug Mark Word null Lock Record to the thread stack to represent the reentrant count? First of all, the number of lock reentrant times must be recorded, because each unlock needs to be locked once. The lock will be released only when the number of lock reentrant times is equal to the number of lock reentrant times. A simple solution is to record the lock reentrant count in the mark Word in the object header, but the mark Word is too small to store this information. The alternative is to just create a Lock Record and Record the number of reentries in it. The reason Hotspot doesn’t do this I guess is because of the efficiency impact: each time a reentrant obtains a Lock, it needs to traverse the stack of that thread to find the corresponding Lock Record and change its value.

So Hotspot eventually chooses to add a Lock Record each time it acquires a Lock to represent the Lock reentrant.

The next see InterpreterRuntime: : monitorenter method

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)) ... Handle h_obj(thread, elem->obj()); assert(Universe::heap()->is_in_reserved_or_null(h_obj()), "must be NULL or an object"); if (UseBiasedLocking) { // Retry fast entry if bias is revoked to avoid unnecessary inflation ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK); } else { ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); }... IRT_ENDCopy the code

The process of fast_enter has been analyzed in the biased Lock article. If the current mode is biased and the biased thread is still using the Lock, it will change the mark word of the Lock to the state of lightweight Lock, and change the Lock Record in the biased thread stack to the corresponding form of lightweight Lock. The code location is biasedLocking. Cpp# 212.

 // All Lock records in the thread stack are traversed while the thread is alive
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    // If the corresponding Lock Record can be found, the biased thread is still executing the code in the synchronized code block
    if (mon_info->owner() == obj) {
      ...
      // Need to upgrade to lightweight Lock, directly modify the bias of the thread stack Lock Record. In order to handle the case of Lock reentrant, set the Hermite of Lock Record to null and the first Lock Record will be processed in the code below
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);
    } else{... }}if(highest_lock ! =NULL) {
    // Change the state of the first Lock Record to no Lock, then set obj's Mark word as the pointer to execute the Lock Recordhighest_lock->set_displaced_header(unbiased_prototype); obj->release_set_mark(markOopDesc::encode(highest_lock)); . }else{... }Copy the code

Let’s look at the slow_enter process.

voidObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); assert(! mark->has_bias_pattern(),"should not see bias pattern here");
  // If there is no lock state
  if (mark->is_neutral()) {
    // Set the product of product product and replace the Mark Word of the object header
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return; }}else
  if(mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { assert(lock ! = mark->locker(),"must not re-lock the same lock"); assert(lock ! = (BasicLock*)obj->mark(),"don't relock with same BasicLock");
    // If the product is reentrant, set the product to null
    lock->set_displaced_header(NULL);
    return; }...// There are multiple threads competing for the lock to expand to a heavyweight lock
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

Copy the code

Lightweight lock release process

CASE(_monitorexit): {
  oop lockee = STACK_OBJECT(- 1);
  CHECK_NULL(lockee);
  // derefing's lockee ought to provoke implicit null check
  // find our monitor slot
  BasicObjectLock* limit = istate->monitor_base();
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  // Iterate through the stack Lock Record from low to high
  while(most_recent ! = limit ) {// If the Lock Record is associated with the Lock object
    if ((most_recent)->obj() == lockee) {
      BasicLock* lock = most_recent->lock();
      markOop header = lock->displaced_header();
      // Release the Lock Record
      most_recent->set_obj(NULL);
      // In bias mode, just release the Lock Record. Otherwise, go through the release process of lightweight or heavyweight locks
      if(! lockee->mark()->has_bias_pattern()) {bool call_vm = UseHeavyMonitors;
        // header! =NULL indicates that the product of the product is not reentrant, and the product needs to be transferred to the Mark Word of the object header
        if(header ! =NULL || call_vm) {
          if(call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) ! = lock) {// CAS failures or heavyweight locks go here, restore obj first and call monitorexitmost_recent->set_obj(lockee); CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception); }}}// Execute the next command
      UPDATE_PC_AND_TOS_AND_CONTINUE(1.- 1);
    }
    // Process the next Lock Record
    most_recent++;
  }
  // Need to throw illegal monitor state exception
  CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
  ShouldNotReachHere();
}
Copy the code

Replace the product of the product’s product with the Mark Word in the object header when releasing the lightweight lock. If the CAS fails or heavyweight lock into InterpreterRuntime: : monitorexit method.

//%note monitor_1 IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)) Handle h_obj(thread, elem->obj()); . ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread); // Free entry. This must be done here, Since a pending exception might be installed on // Release Lock Record elem->set_obj(NULL); . IRT_ENDCopy the code

Monitorexit releases the Lock Record after calling slow_exit.

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
  fast_exit (object, lock, THREAD) ;
}
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  ...
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if (dhw == NULL) {
     // Re-enter the lock and do nothing.return ;
  }

  mark = object->mark() ;

  // If the product of the herbivist product is product of the herbivist product, CAS replaces the product header with the herbivist product
  if (mark == (markOop) lock) {
     assert (dhw->is_neutral(), "invariant");if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return; }}// The exit method of the heavyweight lock is called after the expansion.
  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
Copy the code

If it is a lightweight lock, it will replace mark Word, otherwise it will inflate to a heavyweight lock and call exit. The logic will be explained in the heavyweight lock article.