Please indicate the original source, thank you!

HappyFeet blog

Object.await() and object.notify (); locksupport.park () and locksupport.unpark (); CPP class (which is called when await() releases the lock and notify() reobtains the lock)); And when you look at the implementation of this, you say, gee? How its internal logic is so similar to synchronized! It turned out there was a synchronized low-level implementation.

I used to learn the usage and characteristics of synchronized from the document, but DIDN’t understand its principle. Now I finally know why it has these characteristics!

Not only that, in this process also learned before some knowledge points (object memory layout in the Mark Word part) string together, a kind of suddenly enlightened feeling!

Below to do a collation of synchronized keywords, deepen memory.


First, the foundation

1. Synchronized?

/ * * *@author happyfeet
 * @since Jan 19, 2020
 */
public class SynchronizedUsageExample {

    private static int staticCount;
    private final Object lock = new Object();
    private int count;

    public static synchronized int synchronizedOnStaticMethod(a) {
        return staticCount++;
    }

    public synchronized void synchronizedOnInstanceMethod(a) {
        count++;
    }

    public int synchronizedOnCodeBlock(a) {
        synchronized (lock) {
            returncount++; }}}Copy the code

Synchronized is a simple example, but covers all the uses of synchronized:

  • (1) Applied to static methods
  • (2) act on instance methods
  • (3) Apply to the code block

2. What are the characteristics of synchronized?

  • You always need an object as a lock
    • A lock is a class object when applied to a static method

    • The lock applied to the instance method is the current instance object, which is this

    • When applied to a code block, the lock is the object in parentheses, the Lock object in the above example

  • A lock can be held by only one thread at a time. Different locks do not affect each other

When multiple threads access a synchronized code block, they need to acquire the lock first. Only one thread can acquire the lock. The thread that does not acquire the lock will be blocked until the lock is obtained. Different locks do not affect each other.

Let’s look at an example:

/ * * *@author happyfeet
 * @since Feb 03, 2020
 */
public class SynchronizedLockExample {

    private final Object lock = new Object();

    public static void main(String[] args) {

        SynchronizedLockExample lockExample = new SynchronizedLockExample();

        Thread thread1 = new Thread(() -> lockExample.synchronizedOnCodeBlock(), "thread-1");

        Thread thread2 = new Thread(() -> lockExample.synchronizedOnCodeBlock(), "thread-2");

        Thread thread3 = new Thread(() -> lockExample.synchronizedOnInstanceMethod(), "thread-3");

        Thread thread4 = new Thread(() -> lockExample.synchronizedOnInstanceMethod(), "thread-4");

        sleepOneSecond();
        thread1.start();
        sleepOneSecond();
        thread2.start();
        sleepOneSecond();
        thread3.start();
        sleepOneSecond();
        thread4.start();

        while (true) {}}private synchronized void synchronizedOnInstanceMethod(a) {
        println("I'm in synchronizedOnInstanceMethod, thread name is {}.", Thread.currentThread().getName());
        while (true) {
            // do something}}private void synchronizedOnCodeBlock(a) {
        synchronized (lock) {
            println("I'm in synchronizedOnCodeBlock, thread name is {}.", Thread.currentThread().getName());
            while (true) {
                // do something}}}private static void sleepOneSecond(a) {
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

Execute the main function, the program output is as follows:

The 2020-02-03 T10: then. 993 thread thread - 1: I'm in synchronizedOnCodeBlock, Onconame is thread-1. 2020-02-03T10:19:14.984: I'm in synchronizedOnInstanceMethod, thread name is thread-3.Copy the code

Dump Threads to check the running status of each thread, as shown in the following figure:

Introduction of code

A very simple example, two methods: synchronizedOnInstanceMethod and synchronizedOnCodeBlock (inside the two methods are infinite loop, simulation is processing business logic, the thread will always hold a lock);

SynchronizedOnInstanceMethod methods to this object as a lock, synchronizedOnCodeBlock to lock objects as a lock, and then start the four thread calls the two methods: And thread thread – 1-2 call synchronizedOnCodeBlock method, thread – 3 and thread – 4 call synchronizedOnInstanceMethod method.

Results analysis

From the output of the program, it can be seen that thread-1 and Thread-3 enter the synchronized code block and are in the RUNNABLE state. Because the lock and this locks are held by Thread-1 and Thread-3, respectively, Thread-2 and Thread-4 cannot obtain the corresponding locks, so they are both BLOCKED. If you look closely at the screenshot above, you can see that Thread4 is waiting to acquire the lock on this object.

- waiting to lock <0x000000076acf00f0> (a com.yhh.example.concurrency.SynchronizedLockExample)
Copy the code

Some development

All locks used by synchronized static methods in the same class are class objects; All locks used by synchronized instance methods are this objects; So, for this kind of:

    private synchronized void method1(a) {
        // do something
    }

    private synchronized void method2(a) {
        // do something
    }

    private synchronized void method3(a) {
        // do something
    }
Copy the code

If this lock is held by thread A using A method such as method1(), then other threads calling method1(), method2(), and method3() will block. The same is true for static methods.

The core point here is that a lock can only be held by one thread at a time.

  • reentrant

Synchronized is a reentrant lock, defined as a lock that can be acquired multiple times within a thread.

The greatest benefit of reentrant locks is to avoid deadlocks. Refer to the answer below: Where are Java reentrant locks used?

Two, advanced chapter

3. Synchronized from the perspective of bytecode

As a SynchronizedUsageExample, decompilation yields the following bytecode (omitting some unimportant bits) :

Classfile /Users/happyfeet/projects/java/sampleJava/src/main/java/com/yhh/example/concurrency/SynchronizedUsageExample.class ... { public static synchronized int synchronizedOnStaticMethod(); Descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED // Code: stack=3, locals=0, args_size=0 0: getstatic #4 // Field staticCount:I 3: dup 4: iconst_1 5: iadd 6: putstatic #4 // Field staticCount:I 9: ireturn LineNumberTable: line 14: 0 public synchronized void synchronizedOnInstanceMethod(); Descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED // Code: stack=3, locals=1, args_size=1 0: aload_0 1: DUP 2: getfield #5 // Field count:I 5: iconst_1 6: iadd 7: putfield #5 // Field count:I 10: return LineNumberTable: line 18: 0 line 19: 10 public int synchronizedOnCodeBlock(); descriptor: ()I flags: ACC_PUBLIC Code: stack=4, locals=3, args_size=1 0: aload_0 1: getfield #3 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter // enter the monitor 7: aload_0 8: dup 9: getfield #5 // Field count:I 12: Dup_x1 13: iconST_1 14: iadd 15: putfield #5 // Field count:I 18: ALOad_1 19: monitorexit // exit the monitor 20: Ireturn 21: astore_2 22: ALOAD_1 23: Monitorexit // Exit the monitor (the logic to go when exceptions occur) 24: ALOad_2 25: athrow Exception table: from to target type 7 20 21 any 21 24 21 any LineNumberTable: line 22: 0 ... } SourceFile: "SynchronizedUsageExample.java"Copy the code

By comparing the synchronized modified method with the ordinary method, it can be obtained:

  • The flags for synchronized decorated methods have an additional synchronization flag: ACC_SYNCHRONIZED

  • Pairs of Monitorenter and Monitorexit appear in synchronized code blocks (note: the second Monitorexit in the bytecode above is a processing exception, indicating an exception occurred before the first Monitorexit)

ACC_SYNCHRONIZED, Monitorenter, and Monitorexit

The roles of these are to be found in the JVM specification, mainly in the following areas:

  • 2.11.10. Synchronization

  • 3.14. Synchronization

  • monitorenter

  • monitorexit

The JVM’s synchronization mechanism is implemented through the entry and exit of monitor (monitor lock). Here are the specifications defined by the JVM, not just the Java language, but other languages that can run on the JVM, such as Scala.

For the Java language, a common implementation of synchronization is the use of the synchronized keyword on methods and code blocks.

(1) ACC_SYNCHRONIZED

When synchronized is applied to a method, decompiling into bytecode will find a synchronization flag in the method flags: ACC_SYNCHRONIZED.

For methods with the ACC_SYNCHRONIZED flag, the thread’s steps when executing the method look like this:

  • enters the monitor
  • invokes the method itself
  • exits the monitor whether the method invocation completes normally or abruptly
Monitorenter and Monitorexit directives

Pairs of Monitorenter and Monitorexit directives appear in the bytecode when synchronized acts on a block of code.

Let’s look at how these two instructions are described in the JVM specification:

  • monitorenter

role

Gets the object’s Monitor

parameter

Objectref, which must be an object reference, if null throws an NPE

describe

Each object is associated with a Monitor, which is locked only if it has the owner. When a thread executes monitorenter, it attempts to acquire the monitor associated with the lock as follows:

  • If the entry count of the monitor is 0, the thread can enter the Monitor and set the entry count of the monitor to 1. The thread becomes the owner of the Monitor.
  • If the current thread already owns the Monitor and just reenters, the value of the Monitor’s entry count is incremented by one;
  • If the owner of Monitor is another thread, the current thread blocks until monitor’s entry count is 0 and attempts to acquire monitor again.
  • monitorexit

role

Release the object’s Monitor

parameter

Objectref, which must be an object reference, if null throws an NPE

describe

Perform monitorexit thread must be objectref associated monitor owner, if not throw IllegalMonitorStateException;

Monitorexit reduces the value of entry Count of objectref’s associated monitor by one. When entry Count becomes zero, the current thread releases the object’s Monitor, Is no longer the owner of the monitor.

At this point, synchronized is basically fine; However, there is no complete understanding of the implementation of synchronized in HotSpot, so let’s look at how synchronized is implemented in HotSpot.

Three, into god

5. Biased locks, lightweight locks, and heavyweight locks?

Let’s start with what are biased locks, lightweight locks, and heavyweight locks? How these locks are acquired will be covered later in the implementation of Synchronized in HotSpot.

(1) Biased lock

Biased locking was added in JDK 1.6 to further improve application performance by eliminating uncontested synchronization primitives.

If biased locking is enabled, the lock will be biased to the first thread that acquired it. If the lock is not acquired by another thread in the subsequent execution, the thread holding biased lock will never need to synchronize again.

Once multiple threads compete for the same lock, biased lock must be revoked, and then lightweight lock synchronization operation will be performed after the cancellation, which will bring additional consumption of lock cancellation.

Biased locking can improve the performance of a program with synchronization but no contention, but if most of the locks in the program are always accessed by multiple different threads, the biased pattern is redundant.

JDK 1.6 enables biased locking by default. You can disable biased locking by using -xx: -usebiasedlocking.

Bias lock is to directly modify the lock (flag bit) and biASED_lock (whether bias lock) in the Mark Word, bias thread ID is also directly stored in the lock object’s Mark Word.

(2) Lightweight lock

Lightweight locks were also added in JDK 1.6 to reduce the performance cost of traditional heavyweight locks using operating system mutex in the case of alternating threads.

Lightweight locking creates a space called Lock Record in the stack frame of the current thread, which is used to store a copy of the current Mark Word of the Lock object. Then CAS attempts to update the Mark Word of the object to a pointer to the Lock Record. If the update succeeds, the thread owns the object lock, and the object’s Mark Word lock flag changes to 00, indicating that the object is in a lightweight locked state.

When more than two threads compete for the same lock, the lightweight lock is no longer valid and needs to be inflated to the heavyweight lock. In addition to the mutex overhead, there is the additional locking and unlocking of lightweight locks, so that lightweight locks are slower than traditional heavyweight locks in competitive situations.

So why do you need lightweight locks? Because “for the vast majority of locks, there is no contention for the entire synchronization cycle”, this is a rule of thumb.

(3) Heavyweight locks

The Mark Word of the lock object points to an object pointer of type ObjectMonitor. The essence of Monitor is that it relies on the Mutex Lock implementation of the underlying operating system. When the operating system implements thread synchronization, it needs to make system calls and switch from User Mode to Kernel Mode, which is relatively costly.

The applicable scenarios of the three locks are as follows:

  • Biased lock: Applies to locks acquired by only one thread
  • Lightweight lock: Suitable for multiple threads to acquire locks alternately, there is no competition
  • Heavyweight lock: Applies to multiple threads competing for the same lock

6. Implementation process of synchronized in HotSpot

The main purpose here is to get a general idea of synchronized execution, so a lot of code and comments are omitted (comments are actually important, but have been removed for the sake of space), and only some key C++ source code is retained. For the full C++ source code, you can click the link at the end of the code fragment to see it.

The synchronized HotSpot implementation relies on the object header’s Mark Word. Take a closer look at the Mark Word module in the memory layout of the —- object of the Java Virtual Machine. Here is one of the Mark Word sample images for a 64-bit system:

For synchronized methods, there is an additional ACC_SYNCHRONIZED flag, and the thread executes the method in the following steps:

enters the monitor => invoke method => exits the monitor

It’s not clear exactly what the enters the Monitor do, but I guess it’s the same behavior as the Monitorenter commands; Similarly, exits the monitor is consistent with the Monitorexit directive.

Does Java synchronized have a corresponding bytecode instruction implementation? Threads and Synchronization – The Java Bytecode Implementation

The following is some of their own understanding (there may be wrong place), only for reference.

(1)templateTable_x86_64.cpp#monitorenter:openjdk/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp

Pairs of Monitorenter and Monitorexit directives appear in the bytecodes of synchronized blocks, so how do these two directives work?

See the 7.2.1 Interpreter module and the JVM’s template Interpreter on page 232

The JVM enters the monitorenter instruction with:

void TemplateTable::monitorenter() {
  
	...
    
  // store object__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax); __ lock_object(c_rarg1); .// The bcp has already been incremented. Just need to dispatch to
  // next instruction.
  __ dispatch_next(vtos);
}
Copy the code

Click to view source code

This paragraph is mainly assembly code, very obscure, too difficult to understand.

To be honest, I didn’t understand what this code was doing, so I went straight to the next method: lock_object.

(2)interp_masm_x86_64.cpp#lock_object:openjdk/hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
 assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");

 if (UseHeavyMonitors) {
   call_VM(noreg,
           CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
           lock_reg);
 } else{ Label done; .if (UseBiasedLocking) {
     biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case); }...// Call the runtime routine for slow casecall_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), lock_reg); bind(done); }}Copy the code

Click to view source code

Here also basically didn’t understand, there are a few key words can weigh a: UseHeavyMonitors, UseBiasedLocking, into the next method: InterpreterRuntime: : monitorenter

(3)interpreterRuntime.cpp#monitorenter:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)) ... Handle h_obj(thread, elem->obj()); 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

Click to view source code

An ELEm Object of type BasicObjectLock contains a BasicLock _lock Object and a pointer to Object _obj;

class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  BasicLock _lock;                                    // the lock, must be double word aligned
  oop       _obj;                                     // object holds the lock;

 public:
  // Manipulation
  oop      obj(a) const                                { return _obj;  }
  void set_obj(oop obj)                               { _obj = obj; }
  BasicLock* lock(a)                                   { return&_lock; }... };Copy the code

Click to view source code

  • Here the Object Object is the lock Object;
  • BasicLock _lock Object is mainly used to store the Object header data of the Object pointed to by _obj.
class BasicLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  volatile markOop _displaced_header;
 public:
  markOop      displaced_header(a) const               { return _displaced_header; }
  void         set_displaced_header(markOop header)   { _displaced_header = header; }... };Copy the code

Click to view source code

UseBiasedLocking: indicates whether bias locking is enabled on the JVM. If it is enabled, the fast_enter logic is executed; otherwise, the slow_enter logic is executed.

(4)synchronizer.cpp#fast_enter:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) {
    if(! SafepointSynchronize::is_at_safepoint()) { BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return; }}else{ assert(! attempt_rebias,"can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(! obj->mark()->has_bias_pattern(),"biases should be revoked by now");
 }

 slow_enter (obj, lock, THREAD) ;
}
Copy the code

Click to view source code

Biased lock acquisition

BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);

All this method does is get the bias lock. Click on it, another chunk of code… And it’s not easy to understand…

Refer to the book “Understanding the Java Virtual Machine in Depth” and this blog analysis of synchronized implementation of JVM source code to understand, the general logic is as follows:

(a) Obtain the Mark Word in the object header of the lock object to determine whether it is biased to the state;

Assume that biased locking is enabled on the current VM (enable parameter: -xx :+UseBiasedLocking, which is the default value of JDK 1.6). Then, when the lock object is acquired by a thread for the first time, the VM will set the flag bit in the object header to “01” to enable biased locking.

(b) Judge the biased thread ID in Mark Word: if it is empty, proceed to step (c); If it points to the current thread, it indicates that the current thread is the holder of the bias lock, and can directly execute the synchronized block code without synchronization. If pointing to another thread, go to step (d);

(c) Set the biased thread ID in The Mark Word as the current thread ID through CAS. If the setting is successful, the current thread becomes the holder of the biased lock and can directly execute the synchronized block code; Otherwise enter step (d);

(d) If you go directly from step (b) to step (d), the current thread attempts to acquire a lock that is already in bias mode; Step (d) from step (c) indicates that there are other threads competing for the lock, and the current thread failed to compete. In both cases, the bias mode of the lock ends. When the safepoint is reached, the thread that acquired the bias lock is suspended and the bias lock is revoked. Perform slow_enter to upgrade the lock. A thread blocked at the safe point after the upgrade attempts to acquire the upgrade lock.

Here, the cancellation of biased lock and upgrade of the lock can be divided into two cases according to whether the lock object is currently locked: unlocked (flag bit “01”) and lightweight locked (flag bit “00”).

(5)synchronizer.cpp#slow_enter:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp

A fast_enter failure to obtain the bias lock enters the slow_Enter method, which contains the implementation for obtaining the lightweight lock.

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();

  if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } else
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    lock->set_displaced_header(NULL);
    return; }... lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }Copy the code

Click to view source code

Lightweight lock acquisition

(a) Mark -> neutral() is_neutral() Check whether the mark Word is unlocked, if so, enter step (c), otherwise enter step (b);

(b) If the current thread is the holder of a lightweight lock, it can execute synchronized block code without synchronization; Otherwise, enter step (d);

(c) Set the Mark Word of the lock object to lock through CAS. If the setting is successful, the current thread becomes the holder of the lightweight lock and can directly execute the synchronized block code; Otherwise enter step (d);

(d) If you go directly from step (b) to step (d), the current thread is competing for the same lock with a thread that is already in a lightweight lock; Step (d) from step (c) indicates that there are other threads competing for the lock, and the current thread failed to compete. If two or more threads compete for the same lock, the lightweight lock is no longer valid, and the inflate lock is performed to the heavyweight lock and the status of the lock flag is changed to “10.”

(6)synchronizer.cpp#inflate:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
			...
  for (;;) {
      const markOop mark = object->mark() ;

      // The mark can be in one of the following states:
      // * Inflated - just return
      // * Stack-locked - coerce it to inflated
      // * INFLATING - busy wait for conversion to complete
      // * Neutral - aggressively inflate the object.
      // * BIASED - Illegal. We should never see this

      // CASE: inflated
      if(mark->has_monitor()) { ObjectMonitor * inf = mark->monitor() ; .return inf ;
      }

      // CASE: inflation in progress - inflating over a stack-lock.
      // Some other thread is converting from stack-locked to inflated..if (mark == markOopDesc::INFLATING()) {
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }

      // CASE: stack-locked.if (mark->has_locker()) {
          ObjectMonitor * m = omAlloc (Self) ;
          m->Recycle();
          m->_Responsible  = NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class

          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if(cmp ! = mark) { omRelease (Self, m,true);continue ;       // Interference -- just retry}... markOop dmw = mark->displaced_mark_helper() ; assert (dmw->is_neutral(),"invariant"); m->set_header(dmw) ; m->set_owner(mark->locker()); m->set_object(object); object->release_set_mark(markOopDesc::encode(m));// Hopefully the performance counters are allocated on distinct cache lines
          // to avoid false sharing on MP systems ...
          if(ObjectMonitor::_sync_Inflations ! =NULL) ObjectMonitor::_sync_Inflations->inc() ;
          TEVENT(Inflate: overwrite stacklock) ;
          if (TraceMonitorInflation) {
            if (object->is_instance()) {
              ResourceMark rm;
              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); }}return m ;
      }

      // CASE: neutral
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle();
      m->set_header(mark);
      m->set_owner(NULL);
      m->set_object(object);
      m->OwnerIsThread = 1 ;
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class

      if(Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) ! = mark) { m->set_object (NULL); m->set_owner (NULL); m->OwnerIsThread =0 ;
          m->Recycle() ;
          omRelease (Self, m, true); m =NULL ;
          continue ;
          // interference - the markword changed - just retry.
          // The state-transitions are one-way, so there's no chance of
          // live-lock -- "Inflated" is an absorbing state.
      }

      // Hopefully the performance counters are allocated on distinct
      // cache lines to avoid false sharing on MP systems ...
      if(ObjectMonitor::_sync_Inflations ! =NULL) ObjectMonitor::_sync_Inflations->inc() ;
      TEVENT(Inflate: overwrite neutral) ;
      if (TraceMonitorInflation) {
        if (object->is_instance()) {
          ResourceMark rm;
          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
            (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); }}returnm ; }}Copy the code

Click to view source code

Lock expansion process

This code looks long at first glance, really long, but this is the version with a lot of cuts. On closer inspection, this code is easy to understand:

(a) First, the code ends up getting an ObjectMonitor object, whose Enter method is later called.

And it’s a spin method: for (;;) {}

(c) Then it lists mark’s five states and the operations performed in those five states (actually the fifth state is unreachable) :

      // The mark can be in one of the following states:
      // *  Inflated     - just return
      // *  Stack-locked - coerce it to inflated
      // *  INFLATING    - busy wait for conversion to complete
      // *  Neutral      - aggressively inflate the object.
      // *  BIASED       - Illegal.  We should never see this
Copy the code
  • Vehicle: State that you have Inflated, return to your vehicle.
  • Stack-locked: When two or more threads are competing for the same lock, inflate to a heavyweight lock. Mark is in a lightweight lock, stack-locked, and needs to inflate to a heavyweight lock. Note that each thread competing for a lock will attempt to inflate the lock through CAS (CAS sets the MARK state to INFLATING). The thread with successful CAS will continue to perform the INFLATING operation, while the thread with failed CAS will cancel the previous preparation operation and enter spin to wait for the completion of inflation.
  • INFLATING: Indicates that other threads are INFLATING and the current thread spins until the INFLATING process is complete. The spin here does not occupy CPU resources all the time, it gives up CPU resources through OS ::NakedYield() every once in a while, or suspends itself by calling park(). When another thread finishes bulking the lock, it exits the spin and returns.
  • Neutral: Mark is unlocked, so this branch expands mark’s unlocked state to a heavyweight lock. We reverted to the slow_enter method and checked its calling process. It could be analyzed that Mark would not perform the expansion operation in the Netural state. So if the upstream slow_enter method is not going to go through this branch, this branch should be used to support calls elsewhere; The idea here is similar to stack-locked, with CAS + spin performing inflation, if you are interested.
  • BIASED: Indicates the unreachable state.

(d) When the lock expansion is complete and the corresponding ObjectMonitor object is returned, it does not mean that the thread is competing for the lock. The real lock contention occurs in the ObjectMonitor:: Enter method.

(7)objectMonitor.cpp#enter:openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::enter(TRAPS) {
  Thread * const Self = THREAD ;
  void * cur ;

  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);if (cur == NULL) {
     // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
     assert (_recursions == 0   , "invariant"); assert (_owner == Self,"invariant");// CONSIDER: set or assert OwnerIsThread == 1
     return ;
  }

  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }

  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0."internal state error");
    _recursions = 1 ;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
    _owner = Self ;
    OwnerIsThread = 1 ;
    return; }...// TODO-FIXME: change the following for(;;) loop to straight-line code.
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()

      EnterI (THREAD) ;

      if(! ExitSuspendEquivalent(jt))break ;

      //
      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
          _recursions = 0 ;
      _succ = NULL ;
      exit (false, Self) ; jt->java_suspend_self(); }... }... }Copy the code

Click to view source code

cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;

The first thing we need to understand is that the cur returned here is the actual original value of &_owner;

(a) If cur == NULL, cMPxchg_ptr is executed successfully.

(c) cur == Self (c) cur == Self (c) cur == Self (c) cur == Self (c) cur == Self

(c) If Self->is_lock_owned ((address)cur), this is also a reentrant, but a special reentrant; Self->is_lock_owned ((address)cur); Self->is_lock_owned ((address)cur); If true, _recursions must be 0 and Self is assigned to _owner. At the same time, _recursions must be 1 and return.

(d) If none of the above conditions is met, there is genuine competition; Then a spin operation is used to compete for the lock: enter the method to compete for the lock, and then judge that during the period of acquiring the lock, if no other thread suspends itself, then the lock is successfully acquired and return. Otherwise, the lock is released and jt-> javA_suspend_self () is executed to wait for the suspended thread to wake itself up.

Now let’s look at the EnterI method, which is where the competitive lock is really implemented.

(8)objectMonitor.cpp#EnterI:openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;

    // Try the lock - TATAS
    if (TryLock (Self) > 0) { assert (_succ ! = Self ,"invariant"); assert (_owner == Self ,"invariant") ;
        assert (_Responsible != Self       , "invariant");return; }...if (TrySpin (Self) > 0) {
        assert (_owner == Self        , "invariant") ;
        assert (_succ != Self         , "invariant") ;
        assert (_Responsible != Self  , "invariant");return ;
    }

    // The Spin failed -- Enqueue and park the thread ...
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // Push "Self" onto the front of the _cxq.
    // Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
    // Note that spinning tends to reduce the rate at which threads
    // enqueue and dequeue on EntryList|cxq.
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // Interference - the CAS failed because _cxq changed. Just retry.
        // As an optional optimization we retry the lock.
        if (TryLock (Self) > 0) { assert (_succ ! = Self ,"invariant"); assert (_owner == Self ,"invariant") ;
            assert (_Responsible != Self  , "invariant");return; }}...for (;;) {

        if (TryLock (Self) > 0) break ;

        // park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }

        if (TryLock(Self) > 0) break; . } UnlinkAfterAcquire (Self, &node) ;if (_succ == Self) _succ = NULL; assert (_succ ! = Self,"invariant");if (_Responsible == Self) {
        _Responsible = NULL ;
        OrderAccess::fence(); // Dekker pivot-point
    }

    if (SyncFlags & 8) {
       OrderAccess::fence() ;
    }
    return ;
}
Copy the code

Click to view source code

Those of you who have read the AQS source code will see that this code behaves very similar to AQS ConditionObject. Here’s how EnterI competes for locks:

(a) TryLock (Self) tries to obtain the lock, if return value > 0, the lock was obtained successfully, return;

(b) TrySpin (Self) tries to obtain the lock by spinning for a short time before joining the team. If the return value is > 0, the lock was obtained successfully.

(c) If the first two operations fail, create an ObjectWaiter node and join the CXQ /EntryList queue first; Joining a team is a spin operation. If joining a team fails, try again to acquire the lock. If it succeeds, return directly. Otherwise, continue to join the team;

(d) At this point, Self is already in the queue, which is another spin operation: TryLock + park(); Try to acquire the lock, if successful, enter (e); Otherwise, it suspends itself and waits to be awakened (when the thread holding the lock releases the lock, the earliest node to enter the wait queue is awakened). When the thread wakes up, execution resumes from the suspended point.

(e) Obtain the lock, remove itself from the queue, return.

At this point, the execution of the Monitorenter is basically over; The process of monitorexit is similar to that of monitorenter. You can refer to the process of Monitorenter. One thing to note here is that when Monitorexit is executed, unpark is called at the end of the code to wake up the thread blocking the EnterI method.

Remaining issues

  • How do biased and lightweight locks count reentrant counts?

Four, conclusion

After reading the underlying source code of synchronized, I went back and forth to read the part about synchronized in “In-depth Understanding of Java Virtual Machine”. I found that my own understanding of synchronized had been in the book originally, and it was very detailed. I could not help thinking of the answer I had seen before: When did your programming skills take off? “It was very insightful.

It took a long time to write this article, but the harvest is really quite a lot. Let’s talk about our feelings:

  • In the beginning, it was the most uncomfortable. Almost everything you encountered was a problem, but gradually you will find that there are many similarities (such as the EnterI method and AQS ConditionObject).
  • The content in Java Language and Virtual Machine Specifications is excellent, so be sure to take the time to finish it
  • In Depth understanding the Java Virtual Machine, 2nd edition: This book is also exceptionally well written and is worth reading again and again. The book recently came out in its third edition, and I’m thinking about getting a copy
  • The book summary is a good one, but it works even better if you practice it yourself

References:

(1) Synchronization

(2) 3.14. Synchronization

(3) 2.11.10. Synchronization

(4) monitorenter

(5) monitorexit

(6) In-depth Understanding of The Java Virtual Machine (2nd edition) by Zhiming Zhou.

(7) Synchronized implementation of JVM source analysis