You should know about Synchronized biased locks, lightweight locks, heavyweight locks, lock upgrade process, spin optimization

Catalog

Preface

Common usage of synchronized

Decorated code block (synchronized code block)

Modification method

Cannot synchronized inherit? (Interlude)

Modifying static methods

Modification class

Java object Mark Word

Bias lock

What is biased lock

Biased lock demonstration

Schematic diagram of deflection lock principle

advantage

Vernacular translation

Lightweight lock

What is lightweight lock

Lightweight diagram

advantage

Vernacular translation

Heavyweight lock

What is a heavyweight lock

Schematic diagram of heavyweight lock principle

Monitor source code analysis

Environment building

Constructor

The process of lock competition

Vernacular translation

Spin optimization

epilogue

Reference material

Preface

There's no doubt that synchronized is the first concurrent keyword we've used, and many blogs are explaining this technology. However, most of the explanations are still at the use level of synchronized. Many people may not know the underlying principles and optimizations. Therefore, this article will analyze a large number of C source code of synchronized, so that you can understand him more thoroughly.

This article will describe why to introduce synchronized, common usage, existing problems and optimization, and start the performance with few words.

Common usage of synchronized

Decorated code block (synchronized code block)

synchronized (object) {
      //Specific code
}

Modification method

synchronized void test(){
  //Specific code
}

 

Cannot synchronized inherit? (Interlude)

Parent class A:

public class A {
    synchronized void test() throws Exception {
        try {
            System.out.println("main Next step sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("main Next step sleep end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Subclass B: (test method not overridden)

public class B extends A {

}

Subclass C: (override test method)

public class C extends A {

    @Override
     void test() throws Exception{
        try {
            System.out.println("sub Next step sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("sub Next step sleep end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 

 

Thread A:

public class ThreadA extends Thread {
    private A a;

    public void setter  (A a) {
        this.a = a;
    }

    @Override
    public void run() {
        try{
            a.test();
        }catch (Exception e){

        }
    }
}

Thread B:

public class ThreadB extends Thread {
    private B b;
    public void setB(B b){
        this.b=b;
    }

    @Override
    public void run() {
        try{
            b.test();
        }catch (Exception e){

        }
    }
} 

Thread C:

public class ThreadC extends Thread{
    private C c;
    public void setC(C c){
        this.c=c;
    }

    @Override
    public void run() {
        try{
            c.test();
        }catch (Exception e){

        }
    }
}

test class:

public class test {
    public static void main(String[] args) throws Exception {
        A a = new A();
        ThreadA A1 = new ThreadA();
        A1.setter(a);
        A1.setName("A1");
        A1.start();
        ThreadA A2 = new ThreadA();
        A2.setter(a);
        A2.setName("A2");
        A2.start();
        A1.join();
        A2.join();

        System.out.println("=============");
        B b = new B();
        ThreadB B1 = new ThreadB();
        B1.setB(b);
        B1.setName("B1");
        B1.start();
        ThreadB B2 = new ThreadB();
        B2.setB(b);
        B2.setName("B2");
        B2.start();
        B1.join();
        B2.join();
        System.out.println("=============");

        C c = new C();
        ThreadC C1 = new ThreadC();
        C1.setName("C1");
        C1.setC(c);
        C1.start();
        ThreadC C2 = new ThreadC();
        C2.setName("C2");
        C2.setC(c);
        C2.start();
        C1.join();
        C2.join();
    }
}

Operation result:

Subclass B inherits parent a, but does not override the test method. ThreadB is still synchronized. The subclass C inherits the parent class A and rewrites the test method, but does not explicitly write synchronized, so this method is not a synchronization method. Only when the synchronized keyword is written explicitly is the synchronization method.

Therefore, it is ambiguous that synchronized cannot inherit this sentence. We just need to remember that if a subclass wants to override the synchronization method of the parent class, the synchronized keyword must be displayed and written out, otherwise it will be invalid.

Modifying static methods

synchronized static void test(){
   //Specific code
}

Modification class

synchronized (Example2.class) {
    //Specific code
 }

 

Java object Mark Word

In the JVM, the layout of objects in memory is divided into three parts: object header, instance data and alignment data, as shown in the following figure:

The display of Mark Word value in different lock states is as follows: (focus on thread id, whether it is biased lock, lock flag bit information)

In 64 bit system, Mark Word takes up 8 bytes, type pointer takes up 8 bytes, a total of 16 bytes. Talk is heap. Show me the code.

  • We want to see Mark Word of Java object. First, we need to load a jar package and add it in pom.xml.
<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.9</version>
</dependency>
  • Create A new object A with A variable x with an initial value of 666.
public class A {
    private int x=666;
}
  • Create a new test class test, which involves the jar just loaded. Let's print the Java object.

import org.openjdk.jol.info.ClassLayout;

public class test {
    public static void main(String[] args) {
        A a=new A();
        System.out.println( ClassLayout.parseInstance(a).toPrintable());
    }
}
  • We found that the object header takes up 12 bytes. Why is it different from the 16 bytes mentioned above.

  • In fact, pointer compression is turned on by default. We need to turn off pointer compression, that is, add - XX: - usedecompressedoops configuration.

  • Again, the object header is found to be 16 bytes.

Bias lock

What is biased lock

Before JDK 1.6, the lock was a heavyweight lock (I'll tell you later, as long as you know that it interacts with the kernel and consumes resources). After 1.6, Java designers found that in many cases there is no competition between multiple threads, so they introduced the concepts of unlocked, biased lock, lightweight lock and heavyweight lock for resource problems. First, biased lock, which means biased, biased. This lock will be biased to the first thread to acquire it.

Biased lock demonstration

  • Create and start a thread. The synchronized keyword is used in the run method. The function is to print this Java object.
public class test {
    public static void main(String[] args) {
         Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this){
                    System.out.println(ClassLayout.parseInstance(this).toPrintable());
                }
            }
        });
        thread.start();
    }
}

The place marked in red is 000. According to the previous Mark Word signs in different states, this is the unlocked state. In theory, a thread uses the synchronized keyword, which should be a biased lock.

  • In fact, biased lock is enabled by default after JDK 1.6, but the startup time is delayed. Therefore, you need to add the parameter - XX:BiasedLockingStartupDelay=0 to start the program immediately when it starts.

  • Rerun the code, find the red mark 101, and compare the marks of Mark Word in different states. It is found that this state is biased lock.

Schematic diagram of deflection lock principle

  • In the thread run method, just after it is synchronized, it will judge whether the current object is biased lock and lock flag. No thread executes the object. We can see whether it is biased lock 0, lock flag 01, i.e. unlocked.

  • The thread will assign its own id to markword, that is, change the original hashcode value to thread id, and change the biased lock to 1, indicating that the thread has an object lock, and can execute the following business logic. If the synchronized execution is completed, the object is still biased to the lock state; if the thread ends, the biased lock will be revoked and the object will be restored to the unlocked state.

  • If the object is locked in the same thread, we only need to compare whether the thread id of the object is the same as the thread id. if the same is the problem of thread lock reentry.

advantage

Locking and unlocking don't need extra cost, and there is only a nanosecond gap compared with executing asynchronous methods.

Vernacular translation

Thread 1 locks the object this. He finds that the object is unlocked. Therefore, he assigns the thread id to the Mark Word field of the object, indicating that the object is dedicated to thread 1. Even if he exits the synchronization code, other threads cannot use the object.

Classmate A went to self study classroom C, he found that there was no one in the classroom, so he wrote A name at the door, indicating that someone is using the classroom at present, so even if he went out to eat, other students could not use the room.

Lightweight lock

What is lightweight lock

In the case of multi-threaded alternate synchronization of code blocks, there is no competition between threads. Using lightweight locks can avoid the performance consumption of the introduction of heavyweight locks.

Lightweight diagram

  • On the basis of biased lock just now, if another thread wants to use the resource in a wrong way, Java memory will immediately revoke biased lock (need to wait for the global security point) and upgrade the lock by comparing whether the thread id is the same.

  • After revoking the lightweight lock, a new lock record will be added in the method stack of thread 1. Mark Word of the object will exchange with the lock record.

advantage

The competing threads will not block, which improves the response speed of the program.

Vernacular translation

On the basis of biased lock just now, another thread also wants to acquire resources, so thread 1 needs to undo biased lock and upgrade to lightweight lock.

Classmate A wrote his name outside the self-study classroom, so classmate B also wanted to use the self-study classroom. He needed to remind classmate A that he could not use the heavyweight lock. Classmate A wiped out the name at the door of the self-study classroom and replaced it with A schoolbag with his own books. In this way, when student A does not use the self-study classroom, student B can also use the self-study classroom, just hang his schoolbag outside. In this way, the students who come to use it next time will know that someone has occupied the classroom.

Heavyweight lock

What is a heavyweight lock

When multiple threads compete, Java memory requests a Monitor object to implement.

Schematic diagram of heavyweight lock principle

On the basis of the lightweight lock just mentioned, thread 2 also wants to apply for resources. It is found that the flag bit of the lock is 00, which is a lightweight lock. Therefore, apply for a Monitor to memory, let MarkWord of the object point to the Monitor address, and point the p ower pointer to the address of thread 1. Thread 2 is placed in the waiting queue. After thread 1 points to the end, release the lock resources.

Monitor source code analysis

Environment building

Let's go to the official website http://openjdk.java.net/ Find the open source code and download it through other ways. The source code is implemented in C, which can be opened through DEV C + + tool. The effect is as follows:

Constructor

Let's take a look at \ hotspot\src\share\vm\runtime\ObjectMonitor.hpp. The files ending with. HPP are some imported packages and declarations, which can be imported by. cpp files later.

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;//Thread reentry times 
    _object       = NULL;//The object that stores this monitor 
    _owner        = NULL;//Identify the thread that owns the monitor 
    _WaitSet      = NULL;//Threads in the wait state will be added to the waitSet 
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;//Single item list when multiple threads compete for locks 
    FreeNext      = NULL ;
    _EntryList    = NULL ;//Threads waiting for lock status will be added to the list 
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

 

The process of lock competition

Let's take a look at \ hotspot\src\share\vm\interpreter\interpreterRuntime.cpp. IRT? Entry? No? Async is the lock competition process.

//%note monitor_1
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
//if (UseBiasedLocking) can be set with parameters {/ / if you can use biased locking, you can enter fast {enter
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {//If it is not possible to use biased lock, i.e. slow ﹣ enter
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
  assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
         "must be NULL or an object");
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

 

 

Slow? Enter the enter method of ObjectMonitor.cpp actually called

void ATTR ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  Thread * const Self = THREAD ;
  void * cur ;

  //Try to set monitor's "owner" as the current thread through CAS operation
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  //If the setting is not successful, return directly
  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 "owner" is equal to the current thread, the reentry number "recursions plus 1" will return directly
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }

  //If the current thread enters the monitor for the first time, set the reentry number "recursions" to 1, and "owner" to the current thread, and 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 ;
  }

 //If the lock is not obtained, spin optimization will be carried out. If the lock is not obtained, it will be put into the list
  // We've encountered genuine contention.
  assert (Self->_Stalled == 0, "invariant") ;
  Self->_Stalled = intptr_t(this) ;

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     assert (_owner == Self      , "invariant") ;
     assert (_recursions == 0    , "invariant") ;
     assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
     Self->_Stalled = 0 ;
     return ;
  }

  assert (_owner != Self          , "invariant") ;
  assert (_succ  != Self          , "invariant") ;
  assert (Self->is_Java_thread()  , "invariant") ;
  JavaThread * jt = (JavaThread *) Self ;
  assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
  assert (jt->thread_state() != _thread_blocked   , "invariant") ;
  assert (this->object() != NULL  , "invariant") ;
  assert (_count >= 0, "invariant") ;

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  Atomic::inc_ptr(&_count);

  EventJavaMonitorEnter event;

  { // Change java thread status to indicate blocked on monitor enter.
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

    DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);
    }

    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);

    Self->set_current_pending_monitor(this);

    // 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();
    }
    Self->set_current_pending_monitor(NULL);
  }

  Atomic::dec_ptr(&_count);
  assert (_count >= 0, "invariant") ;
  Self->_Stalled = 0 ;

  // Must either set _recursions = 0 or ASSERT _recursions == 0.
  assert (_recursions == 0     , "invariant") ;
  assert (_owner == Self       , "invariant") ;
  assert (_succ  != Self       , "invariant") ;
  assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;

  // The thread -- now the owner -- is back in vm mode.
  // Report the glorious news via TI,DTrace and jvmstat.
  // The probe effect is non-trivial.  All the reportage occurs
  // while we hold the monitor, increasing the length of the critical
  // section.  Amdahl's parallel speedup law comes vividly into play.
  //
  // Another option might be to aggregate the events (thread local or
  // per-monitor aggregation) and defer reporting until a more opportune
  // time -- such as next time some thread encounters contention but has
  // yet to acquire the lock.  While spinning that thread could
  // spinning we could increment JVMStat counters, etc.

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

  if (event.should_commit()) {
    event.set_klass(((oop)this->object())->klass());
    event.set_previousOwner((TYPE_JAVALANGTHREAD)_previous_owner_tid);
    event.set_address((TYPE_ADDRESS)(uintptr_t)(this->object_addr()));
    event.commit();
  }

  if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
     ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
  }
} 

 

Vernacular translation

When student a is using the self-study classroom, and student B wants to use the self-study classroom at the same time, there is a competition relationship. So class B joins the waiting queue in the process of running a. If classmate C wants to use the classroom at this time, he will also join the waiting queue. When class A is finished, class B and C will compete for the self-study classroom.

Spin optimization

Spin optimization is relatively simple. If other threads are added to the waiting queue, then it will consume resources to wake up and run the thread, so the designer will let it idle for a while to see if the thread can end in a while, so don't join the waiting queue.

In other words, if classmate a is using the self-study classroom, classmate B can go back to the dormitory, and come back when class A is finished, but it will take an hour for class B to come back to the dormitory, and only 10 minutes for class A. So B can wait for 10 minutes at the door instead of going back to the dormitory to prevent the waste of back and forth time.

epilogue

Oh, my God, it's finally over. I'm so tired. Finally, I have finished synchronized. If there is something wrong, you need to correct it. If you think it's OK, please give me some praise and comment.

Reference material

Why does System.out.println() affect memory visibility in Java

Stop asking about the Java memory model. Look here!

JVM -- assembly instruction set

The use of Synchronized in Java

synchronized synchronization method (08) synchronization does not have inheritance

Thread--synchronized cannot be inherited?!?!!!

 

166 original articles published, 72 praised, 160000 visitors+
Private letter follow

Tags: Java jvm JDK xml

Posted on Mon, 16 Mar 2020 02:40:14 -0400 by SamLiu