preface

Synchronized was, of course, the first concurrency keyword we used, and there are many blog posts on the technique. However, most of the explanation is confined to the use of synchronized, and many of its underlying principles and optimizations may not be known to many people. Therefore, this article will be through a large number of SYNCHRONIZED C source code analysis, so that we understand him more thoroughly.

This article will describe the reasons for introducing synchronized, common ways of using synchronized, existing problems and optimization. Without further words, it will begin to perform.



Visibility issues and resolution

The concept description

When one thread makes changes to a shared variable and the other thread immediately gets the updated value.

The code shown

Class:

Public class Example1 {//1. Creating shared variables private static Boolean flag =true; Public static void main(String[] args) throws Exception {//2.t1 Indicates an empty loop if flag istrueT1 = new Thread(new)Runnable() {
            @Override
            public void run() {
                while (true) {
                    if(! flag){ System.out.println("Enter the if");
                        break; }}}}); t1.start(); Thread.sleep(2000L); //2. T2 change flag tofalse
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                flag = false;
                System.out.println("Modified"); }}); t2.start(); }}Copy the code

Running results:



Analysis of the

Here first to understand the Java memory mode, do not understand the portal can be clicked, todo.

In the following figure, threads T1 and T2 fetch flag=true from main memory, and t1 loops until flag is false. T2 takes the value of flag, changes it to false, and writes it to main memory. At this time, the flag in the working memory of the main memory and thread T2 is false, but the flag in the working memory of thread T1 is still true, so the loop cannot be returned, and the program will continue to execute.



How does Synchronized address visibility

First let’s try adding a print statement to the T1 thread to see what happens.

Code:

Public class Example1 {//1. Creating shared variables private static Boolean flag =true; Public static void main(String[] args) throws Exception {//2.t1 Indicates an empty loop if flag istrueT1 = new Thread(new)Runnable() {
            @Override
            public void run() {
                while (true) {// New print statement system.out.println (flag);if(! flag){ System.out.println("Enter the if");
                        break; }}}}); t1.start(); Thread.sleep(2000L); //2. T2 change flag tofalse
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                flag = false;
                System.out.println("Modified"); }}); t2.start(); }}Copy the code

Running results:



We find that the if statement has been printed out, and thread 1 has sensed thread 2’s change to flag, i.e. the print statement has affected visibility. Why is that?



The answer is println, let’s look at the source code:



Println has a locking procedure, that is, the following operation:

1. Obtain the synchronization lock.

2. Clear variables in your working memory.

3. Obtain the latest value from the main memory and load it to the working memory.

4. Print and output.

So this explains why t1 can sense t2’s changes to flag immediately after thread T1 adds a print statement. Since it gets the latest value from main memory every time it prints, t1 immediately gets the value from main memory when T2 changes, so it enters the if statement and finally breaks out of the loop.

The principle of synchronized is to clear the value of its own working memory, and refresh the latest value of the main memory into the working memory, so that each thread can perceive the modification of each other.

Atomicity problem and its solution

The concept description

In one or more operations, either all operations are performed or none are performed.

The code shown

Class:

Public class Example2 {//1. Define global variables number private static int number = 0; public static void main(String[] args) throws Exception { Runnable runnable = () -> {for(int i = 0; i < 10000; i++) { number++; }}; T1 = new Thread(runnable); t1.start(); T2 = new Thread(runnable); t2.start(); //4. Wait for t1,t2 to finish t1.join(); t2.join(); System.out.println("number="+ number); }}Copy the code

Running results:



Analysis of the

Each thread executes logic that loops 10,000 times, incrementing each time, so we want 20,000, but it turns out to be less than 20,000. Let’s disassemble with the javap command first. We see a lot of code, but number++ involves four lines of instructions. See the second figure.





If there are multiple threads executing number++, the current number is 0, thread 1 executes iconst_1, which is about to execute iadd, thread 2 executes getStatic, which has not changed the number value, so thread 2 gets the static field 0. When thread 1 completes the iadd operation, the number changes to 1. When thread 2 completes the iadd operation, the number remains 1. So that’s where you see the problem, you do number++ twice, but the number only increases by one.

In concurrent programming, atomicity problems arise when one thread is halfway through working on a shared variable and another thread is also likely to work on the shared variable.

How does synchronized solve the atomicity problem

In the above analysis, we already know the cause of the problem, number++ is made up of four instructions that do not guarantee atomic operations. So, we just have to keep number++ as a whole, to keep it atomic. The specific code is as follows:

Public class Example2 {//1. Define global variables number private static int number = 0; Private static object object = new object (); public static void main(String[] args) throws Exception { Runnable runnable = () -> {for(int i = 0; i < 10000; I++) {synchronized (object) {number++; }}}; T1 = new Thread(runnable); t1.start(); T2 = new Thread(runnable); t2.start(); //4. Wait for t1,t2 to finish t1.join(); t2.join(); System.out.println("number="+ number); }}Copy the code



Synchronized is the correct result if the final number is 20000. Example2 decompiles and finds monitorenter and monitorexist before and after four lines of instructions. While thread 1 is executing an intermediate instruction, other threads cannot access monitorenter and wait until thread 1 finishes monitorexist. Only other monitorenter processes can continue to add to monitorenter.



Order problem and solution

The concept description

The order in which the program executes in code, which Java optimizes both when compiled and when run, can result in the order in which we end up executing the code in a way that is not the order in which we wrote it.

The code shown

Let’s start with the concept of reordering, where the order in which statements are executed is rearranged. It is mainly divided into three types:

1. Compiler optimized reordering: the execution order of statements can be rearranged.

2. Reordering of instruction level parallelism: Modern processors use instruction level parallelism to execute multiple instructions on top of each other.

3. Reordering of memory systems: Since the processor uses caches and read/write buffers, it may appear to be out of order.

A = new a (); May be decomposed by the JVM into the following code:

// Allocate memory=allocate(); // Set s to point to the newly allocated address of the copy codeCopy the code

// The three steps above may be reordered to 1-3-2, i.e., 1 memory=allocate(); // Set s to the newly allocated address 2 ctorInstanc(memory) // initializes the object copy codeCopy the code

Once such A reorder is assumed, let’s say thread A is executing steps 1 and 3, but step 2 is not finished yet. Thread B enters the first statement and determines that a is not null, so it simply returns a. In fact, this is an uninitialized A, so there’s a problem.

How does synchronized solve the ordering problem

Add the synchronized keyword to the above three steps, and you won’t have a problem if reordering occurs. When thread A performs steps 1 and 3, thread B cannot access the first statement because it cannot acquire the lock. Thread B can obtain the lock again and perform related operations only after thread A has released the lock.

A common use of synchronized

Modify code blocks (synchronized code blocks)

Synchronized (object) {// synchronizedCopy the code

Modification methods

synchronized void test(){// specific code}Copy the code

Synchronized can’t inherit? (incident)

The parent class A:

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

Subclass B:(unoverridden test method)

public class B extends A {

}Copy the code

Subclass C:(override test method)

public class C extends A {

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

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){

        }
    }
}Copy the code

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){

        }
    }
} Copy the code

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){

        }
    }
}Copy the code

The test class test:

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(); }}Copy the code

Running results:



Subclass B inherits parent class A, but does not override the test method. ThreadB is still synchronous. Subclass C inherits parent class A and overrides the test method, but does not explicitly write synchronized, so this method is not synchronized. A synchronized method is defined only by explicitly writing the synchronized keyword.

If a subclass wants to override the synchronized method of its parent class, the synchronized keyword must be displayed or invalid.

Decorated static methods

synchronized static void test(){// specific code}Copy the code

decorator

Synchronized (example2.class) {// specific code}Copy the code

Java object Mark Word

In the JVM, the layout of objects in memory is divided into three areas: object headers, instance data, and alignment data, as shown below:



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



On a 64-bit system, the Mark Word takes up 8 bytes and the type pointer takes up 8 bytes, making a total of 16 bytes. Talk is cheap. Show me the code. Let’s look at the code.

  • If we want to see the Mark Word for a Java object, we need to load a JAR package and add it to pom.xml.

<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.9</version>
</dependency>Copy the code
  • Create A new object, A, with variable x starting with 666.

public class A {
    private int x=666;
}Copy the code
  • Create a new test class, Test, that involves the JAR you just loaded, and 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()); }}Copy the code
  • We found that the object header occupied12A byte? Why is it different from 16 bytes?



  • In fact, pointer compression is enabled by default, so we need to turn off pointer compression, that is, add-XX:-UseCompressedOopsConfiguration.




  • Execute again and find that the object header is 16 bytes.



Biased locking

What is biased locking

Before JDK1.6, locks were heavyweight locks (as long as we know that they interact with the kernel and consume resources). After 1.6, Java designers realized that there were many cases where there were no multiple threads competing, so they introduced the concepts of no-lock, biased locking, lightweight locking, and heavyweight locking to solve resource problems. Let’s say biased lock, which means biased, favoritism, the lock is biased in favor of the first thread that gets it.

Biased locking demo

  • Create and start a thread that prints this Java object using the synchronized keyword run.

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(); }}Copy the code

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




  • Bias locking is actually enabled by default after JDK1.6, but the startup time is delayed, so you need to add the parameter-XX:BiasedLockingStartupDelay=0Make it start as soon as the program starts.



  • Re-run the code and find 101 marked in red. Compare the Mark Word in different states and get that this state is biased lock.



Principle diagram of deflection lock

  • In the run method of the thread, when synchronized is executed, it will judge whether the current object is biased lock and lock flag. If no thread executes the object, we can see whether the biased lock is 0 and the lock flag flag is 01, that is, there is no lock state.



  • The thread will assign its id to Markword, that is, the original hashCode value will be changed to the thread ID, whether the bias lock is changed to 1, indicating that the thread owns the object lock and can execute the following business logic.If synchronized completes, the object is still biased toward the lock state; If the thread terminates, the bias lock is revoked, restoring the object to a lock-free state.



  • If the object is locked again in the same thread, we just compareObject thread IDWhether or notThread idIf yes, it is a thread lock reentrant problem.

advantage

Locking and unlocking require no additional cost, with a nanosecond difference compared to performing asynchronous methods.

Vernacular translation

Thread 1 locks the object this. It finds that the object is unlocked, so it assigns the thread ID to the Mark Word field of the object, indicating that the object is exclusive to thread 1. Even if thread 1 exits the synchronization code, other threads cannot use the object.

Student A went to study room C, and found no one was in the room, so he wrote his name on the door to indicate that the room was being used, so that even if he went out to eat, other students could not use the room.

Lightweight lock

What is a lightweight lock

In the case of multiple threads alternately synchronizing blocks of code, there is no contention between threads, and using lightweight locks avoids the performance drain introduced by heavyweight locks.

Lightweight diagram

  • On the basis of the bias lock, if another thread also wants to use the resource at the wrong peak, the Java memory will immediately revoke the bias lock (wait for the global safety point) and upgrade the lock by comparing whether the thread ID is the same.



  • When the lightweight lock is revoked, a new lock record is added to thread 1’s method stack, and the object’s Mark Word is exchanged with the lock record.



advantage

Avoiding direct use of heavyweight locks when threads are not competing improves program responsiveness.

Vernacular translation

On top of the biased lock, another thread wants to acquire the resource, so thread 1 needs to undo the biased lock and upgrade to a light lock.

Student A wrote his name outside the self-study classroom, so student B also wanted to use the self-study classroom. He needed to remind student A that biased lock could not be used. Student A erased the name at the door of the self-study classroom and replaced it with A bag containing his own books. In this way, when student A does not use the self-study classroom, student B can use the self-study classroom, just need to hang his bag outside. This way, the next student who uses it will know that the room has been occupied.

Heavyweight lock

What is a heavyweight lock

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

Heavyweight lock principle diagram

Thread 2 also wants to apply for resources on the basis of the lightweight lock. It finds that the flag bit of the lock is 00, that is, the lightweight lock. Therefore, it applies to the memory for a Monitor, makes the object MarkWord point to the address of the Monitor, and points the OWER pointer to the address of thread 1. When thread 1 points to completion, release the lock resource.



Monitor source code analysis

Environment set up

We can go to http://openjdk.java.net/ to find the open source code, or download it through other channels. Source code is C, can be opened through the DEV C++ tool, the effect is as follows:



The constructor

Let’s take a look at \hotspot\ SRC \share\vm\ Runtime \ objectMonitor. HPP. Files ending in. HPP are packages and declarations that can then be imported by. CPP files.

 ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; // thread reentrant count _object = NULL; // Store the monitor object _owner = NULL; // Identifies the thread that owns the monitor _WaitSet = NULL; / / inwaitThe thread in state is added to _waitSet _WaitSetLock = 0; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL; _EntryList = NULL ; _SpinFreq = 0; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }Copy the code

Lock the process of competition

\hotspot\ SRC \share\vm\interpreter\interpreterRuntime. CPP, IRT_ENTRY_NO_ASYNC is a lock contention 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"); // Whether to use bias lock, you can add parameters to setif(UseBiasedLocking) {// If biased locking can be used, fast_enter // Retry fast entry is enteredif bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else{// If bias locks cannot be used, 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_ENDCopy the code

Slow_enter actually calls the enter method of objectMonitor.cpp

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 the _owner of monitor to the current thread cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL); // If the setting fails, return directlyif (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 number _recursions plus 1 is returnedif (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return; } // If the current thread enters the monitor for the first time, set _recursions to 1 and _owner to the current threadif (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 no lock is obtained, spin optimization is performed, if no lock is obtained, add it to 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() ;
  }
} Copy the code

Vernacular translation

When student A uses the self-study room, student B also wants to use the self-study room at the same time, so there will be competition. So student B joins the waiting queue while A is running. If student C also wants to use the classroom, he will also join the waiting queue. When student A finishes using the classroom, students B and C will compete for self-study room.

Spin optimization

Spin optimization is relatively simple. If another thread is queued, it consumes resources to wake up and run the thread later, so the designer lets it idle for a while to see if the thread can finish up after a while, so it doesn’t queue again.

In plain English, if student A is using the self-study room, student B can go back to the dormitory and come back when student A is finished using it, but the process of student B returning to the dormitory and coming back takes one hour, while student A is finished in 10 minutes. Therefore, B can not go back to the dormitory, but wait at the door for 10 minutes, in order to prevent the waste of time.

conclusion

Oh, mom, finally over, tired to death. Finally synchronized finished writing, if there is an incorrect place, but also need you correct. If you think it’s good, please give me a thumbs up and a comment.

And a focus on



The resources

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

Stop asking what the Java memory model is, look here!

JVM– Assembly instruction set

Use of Synchronized in Java

Synchronized does not have inheritance

Thread–synchronized can’t be inherited? ! ? !!!!!!!!!