Synchronized is used every day, do you know the implementation principle?

Source: Xiao Xiaomu 's blog www.cnblogs.com/wyc1994666/p/11748212.html

The Synchronized keyword can be regarded as a veteran lock of Java. At the beginning, it supported the synchronization task of Java. Its usage is simple and rude and easy to learn. But some knowledge points related to it still need to be mastered by our developers.

For example, we all know that synchronized locks can be used to implement mutual exclusion functions, which can be used in methods or code blocks. Then, how to implement different usages, and what optimizations have been experienced, require a solid understanding.

  • 1. Basic usage

  • 2. Implementation principle

  • 2.1 Implementation of synchronous code block

  • 2.2 Implementation of the synchronization method

  • 3. Lock upgrade

  • 3.1 Introduction to Java Object Header

  • 3.2 What is lock upgrade

1. Basic usage

Usually we can use Synchronized in a method or code block, and the method has ordinary methods or static methods.

For ordinary synchronization methods, the lock is the current instance object, which is this

public class TestSyn{
  private int i=0;
  public synchronized void incr(){
    i++;
  }
}

For static synchronization methods, the lock is a Class object

public class TestSyn{
  private static int i=0;
  public static synchronized void incr(){
    i++;
  }
}

For synchronized code blocks, the lock is an object in the synchronized code block

public class TestSyn{
  private  int i=0;
  Object o = new Object();
  public  void incr(){
    synchronized(o){
        i++;
    }
  }
}

2. Implementation principle

The implementation principle of Synchronized is introduced in the JVM specification . JVM implements method synchronization and code block synchronization based on entering and exiting the Monitor object, but the implementation details of the two are different.

Code block synchronization is implemented using monitorenter and monitorexit instructions, while method synchronization is implemented in another way, through a method flag (flag) ACC_SYNCHRONIZED.

2.1 Implementation of synchronous code block

monitorenter and monitorexit

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter (reference source)

Let's look at the introduction of monitorenter and monitorexit in the JVM specification

Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,

Each object has a monitor (Moniter) associated with it. The thread executing the moniterenter instruction will obtain the ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread will wait Until the object is unlocked.

A monitorenter instruction may be used with one or more monitorexit instructions to implement a synchronized statement in the Java programming language. The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods

Here comes the point. The above paragraph introduces two points:

  • Use monitorenter and monitorexit instructions to implement synchronous code blocks in the Java language (code examples are provided later)

  • The monitorenter and monitorexit commands are not used in the synchronization method! ! !

2.2 Implementation of the synchronization method

First look at what the JVM specification says

https://docs.oracle.com/javase/specs/jvms/se6/html/Compiling.doc.html#6530 (reference source)

A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the runtime constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly.

The above passage mainly talks about a few points:

  • The synchronization method is not implemented based on the monitorenter and monitorexit instructions

  • In the runtime constant pool, ACC_SYNCHRONIZED is used to distinguish whether it is a synchronous method. This flag is checked when the method is executed

  • When a method has this flag, the incoming thread first needs to obtain a monitor to execute the method

  • The monitor will be released when the method ends or throws an exception

public class TestSyn {

    private int i=0;

    // 同步方法
    public synchronized void incer(){
        i++;
    }

    // 同步代码块
    public  void decr(){
        synchronized (this) {
            i--;
        }
    }
}

You can decompile the bytecode to see how the bottom layer is implemented

// 得到字节码
javac TestSyn.java
// 反编译字节码
javap -v TestSyn.class

The decompilation result of the synchronization code block is as follows:

The decompilation result of the synchronization method is as follows:

3. Lock upgrade

3.1 Introduction to Java Object Header

Object memory layout

In our common HotSpot virtual machine, the object consists of three parts, namely the object header, instance data, and alignment padding.

The object header is the part related to the lock information. The runtime data of the object will be stored in the object header, including hash, GC generation age, and lock status (no lock, partial lock, lightweight lock, heavyweight lock ), whether it is biased towards locks, biased towards thread ID and other information.

The area that stores the above is called Mark Word . In addition to this part of the object header, there is also a part of the area used to store the type pointer, which can be used to locate the metadata information of the object. Let's focus on the memory layout of the object header, because this part is related to us this time.

The representation of the object in memory is as follows:

The structure of the object header is shown as follows:

The mark word representation is as follows:

3.2 What is lock upgrade

Here is an example of grabbing the pit to explain the lock upgrade process.

When only one thread accesses it is called a biased lock

Assuming we have a key for each toilet, we must first obtain a lock in order to use the toilet. One morning, employee A hurriedly finished his card to go to the toilet, and posted a label of "work number 007 in use" on the toilet door, indicating that it is currently occupied by an employee with work number 007 (equivalent to thread id). When he enters again, as long as the above label still shows the work number 007, he can enter at will, and he does not need to lock it again. It is a bit biased toward the work number 007 employee, so this is called a biased lock.

Upgrade to lightweight lock when contention occurs (spin waiting)

When Employee A was using the toilet, two people came and wanted to use the toilet, but found that the toilet was being used by someone and could not get the lock. So they can only wait outside for A to come out. The process they wait is called "spinning" and this is called a lightweight lock.

Then there is another question. When A came out, who was waiting for the two people to live locked in? There are two ways, queuing in the order of arrival or not queuing, both of which can be achieved. The former is called a fair lock and the latter is called an unfair lock.

Upgrade to a heavyweight lock when the spin wait has no results

But after spinning for a while, the two people discovered that A hadn't come out yet (the JDK1.6 stipulated 10 times). It's not a way to wait so long, so I plan to upgrade, ask the toilet administrator (operating system) for feedback, and upgrade to A heavyweight lock.

There are a total of four lock states, unlocked state, biased lock, lightweight lock and heavyweight lock. With the competition of locks, locks can be upgraded from biased locks to lightweight locks, and then upgraded heavyweight locks. Also pay attention to the public number Java technology stack reply JVM46 to obtain a 46-page JVM tuning tutorial.

The mark word changes during the lock upgrade process are as follows:

Bias lock

Biased lock is also a lock optimization introduced in JDK 1.6. It was introduced to optimize lock elimination in scenarios without lock contention. For example, a piece of synchronization code is always called by a single thread. In this scenario, there is no need to use a synchronization lock. The synchronization lock referred to here does not refer to synchronized, but to the operating system level mutex.

The bias of the bias lock means that the synchronization code will always be biased towards the first thread that calls it until another thread competes for the lock. When the synchronization code is called for the first time and the lock is obtained, it will lock the object header and stack frame. The Id of the biased thread is stored in the Lock Record, and the thread does not need to reapply for the lock when it enters here. It only needs to check whether the ID pointing to the thread is stored in the Mark Word of the object header.

The bias lock will revoke the bias until there is another thread competing for the lock.

Lightweight lock

Lightweight lock is a new type of lock mechanism added in JDK 1.6. The "lightweight" in its name is relative to the operating system.

In terms of traditional locks implemented by mutexes, traditional locking mechanisms are called "heavyweight" locks. It is not used to replace heavyweight locks. Its original intention is to use operating system mutexes in the heavyweight locks of the system.

Before the thread executes the synchronization block, the JVM will first create a space for storing the lock record in the stack frame of the current thread, and copy the Mark Word in the object header to the lock record, which is officially called Displaced Mark Word.

Then the thread tries to use CAS to replace the Mark Word in the object header with a pointer to the lock record. If it succeeds, the current thread obtains the lock. If it fails, it means that other threads compete for the lock. The current thread tries to use spin to obtain the lock. Spinning in place, if the number of spins reaches 10 times, it will be upgraded to a heavyweight lock.

Heavyweight lock

Competing threads will be upgraded to heavyweight locks after spinning for a period of time and fail to acquire the lock. At this time, the lock acquisition and release will be allocated by the operating system. If the thread holding the lock releases the lock, the operating system will wake up all blocked Which thread enters a new round of contention mode, it should be noted that these blocked threads do not obtain the priority of the lock, which means that the synchronized lock is unfair.

In addition, synchronized is also insensitive to interrupt operations. It will not give up blocking waiting because it is interrupted. It will either get the lock or keep blocking.

Recommend to read more on my blog:

1. A series of tutorials on Java JVM, collections, multithreading, and new features

2. Spring MVC, Spring Boot, Spring Cloud series of tutorials

3. Maven, Git, Eclipse, Intellij IDEA series tool tutorial

4. The latest interview questions from major manufacturers such as Java, back-end, architecture, and Alibaba

Feel good, don’t forget to like + forward!

Finally, pay attention to the WeChat official account of the stack leader: Java technology stack, reply: Welfare, you can get a free copy of the latest Java interview questions I have compiled for 2020. It is really complete (including answers), without any routine.

Guess you like

Origin blog.csdn.net/youanyyou/article/details/108360538