The premise

Object head

First we need to understand what an object header is, because synchronized lock information is stored in the object header. The object header is composed of MarkWord and KlassWord, and the information of our lock is stored in Markword, so it is ok to focus on Markword. The following figure shows the structure of MarkWord on 32-bit and 64-bit virtual machines. Very important. Make sure you write it down.

Here I focus on 64-bit virtual machines. We can see from the figure that the size of MarkWord on a 64-bit VM is 8byte (56bit + 1bit + 4bit + 1bit + 2bit = 64bit = 8byte), which will be used later.

Lock is introduced

From the above figure, it is easy to see that a simple Sychronized system includes various lock states (no lock, biased lock, lightweight lock, and heavyweight lock). These lock states are optimized for Sychronized.

The preparatory work

To make the results more obvious, we first reference JOL and then programmatically print out the object information we need using IDEA. Maven

    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.8</version>
    </dependency>
Copy the code

Then create A Class A

    public class A {
        boolean b = false;
    }
Copy the code

The test program

    package syc;
    import org.openjdk.jol.info.ClassLayout;
    import org.openjdk.jol.vm.VM;

    import static java.lang.System.out;
    public class SycTest {
        static A a = new A();
        public static void main(String[] args) { out.println(VM.current().details()); System.out.println(ClassLayout.parseInstance(a).toPrintable()); }}Copy the code

If you are too lazy, you can also read my test results and analysis.

unlocked

The above code runs as follows:

When a new object is created, if the class to which the object belongs does not turn off biased locking mode, the Mark Word of the newly created object will be in biased state. At this time, the Thread ID (see mark Word format in biased state above) in Mark Word is 0, and the lock flag bit is 01. Indicates that there is no bias to any thread. This is also called Anonymous Bias. We only look at the first 8 bits (00000001), based on the Mark Word structure diagram analysis

placeholder age Whether or not to Lock flag bit
0 0000 0 01

Biased locking

Biased locks have no way of issuing the provider thread mutexes in the first place, so why reference this lock?

Because in most cases, blocks of code decorated with synchronized that we assume have concurrency problems are actually running on only one thread. Therefore, using lightweight lock directly will have performance loss, because the locking or unlocking of lightweight lock requires a CAS operation, while the unlocking of biased lock does not need to modify the markword of the object header, so there is one less CAS operation. If the current thread repeatedly calls a locking method and finds that its object already has biased records in its favor, it simply runs the code. These operations can improve efficiency.

So, will bias locks be released after executing a block of code? Exit the synchronized code block with the bytecode instruction monitorexit. When the JVM processes this instruction, the first step is to retrieve all lock records in the current thread stack related to the current lock and release the last lock record. By checking the Markword of the lock object, do nothing if the current lock object is biased towards the lock state. That is, even if the thread exits the synchronized block, the lock object retains a biased lock that is biased toward the current state.

If A lock object was previously acquired by thread A, its lock state is biased toward A. The dummy thread B also needs to acquire the lock object. Since the lock object is not biased towards thread B, the lock upgrade logic will be triggered. Thread B triggers the logic that favors locking to upgrade lightweight locks.

Lightweight lock

JVM developers have found that, in many cases, code in synchronized blocks is uncontested while Java programs are running, with different threads alternately executing code in synchronized blocks. In this case, there is no need to block the thread (heavyweight lock). So the JVM introduced the concept of lightweight locking.

When biased lock is disabled or the lock is upgraded to lightweight due to multiple threads competing for biased lock, an attempt is made to obtain the lightweight lock. The procedure is as follows: Obtain the lock

  1. Determines whether the current object is in a lock-free state (hashcode, 0, 01). If so, the JVM first creates a space called a Lock Record in the current thread’s stack frame. The product is used to store the copy of the product of the herbier product (namely the product Mark Word) of the Lock object, copy the product of the product to Lock Record, and point the owner in Lock Recod to the current object.
  2. The JVM uses the CAS operation to try to update the object’s Mark Word to a pointer to a Lock Record. If a Lock is successfully contested, the Lock flag bit is changed to 00 and the synchronization operation is performed.
  3. If CAS fails in step 2, it will determine whether the Mark Word of the current object points to the stack frame of the current thread. If it does, it means that the current thread has held the lock of the current object, and the synchronization code block will be directly executed. Otherwise, it means that the lock object has been preempted by another thread. In this case, the light lock needs to expand the light weight lock, the lock flag becomes 10, and the waiting thread will enter the blocking state.

Release of lightweight locks

Lightweight lock release is also done through CAS operations, with the main steps as follows:

  1. Retrieve data stored in the lightweight lock of herbivore herbivore product;
  2. Replace the retrieved data in the Mark Word of the current object with the CAS operation. If the CAS operation succeeds, the lock is released successfully.
  3. If the CAS operation fails to be replaced, another thread attempts to obtain the lock. In this case, you need to expand the lightweight lock to a heavyweight lock.

Heavyweight lock

Heavyweight locks are the traditional locks that use the underlying synchronization mechanism of the operating system to implement thread synchronization in Java. Synchronized is implemented by using monitor objects, CAS and MUtex mutexes. There are internal wait queues (CXQ and EntryList) and conditional wait queues (waitSet) to store the corresponding blocked threads.

The thread that does not compete for the lock is stored in the wait queue, and the thread that has obtained the lock is stored in the conditional wait queue after invoking wait. Unlock and notify will wake up the waiting thread in the corresponding queue to compete for the lock.

However, because blocking and wake up depend on the implementation of the underlying operating system, the system call has a switch between user state and kernel state, so it has a high overhead, so it is called heavyweight lock.

Therefore, adaptive spin mechanism is introduced to improve the performance of the lock.

spinlocks

When monitor implements locking, it blocks and wakes up threads, which requires the CPU to change from user state to core state. Frequent blocking and wakes up are heavy work for the CPU, and these operations put great pressure on the concurrency of the system. At the same time, research shows that shared data is locked only for a short period of time, and it is not worth blocking and waking up threads for this period. So, by having two or more threads executing in parallel at the same time, we can tell the next thread that requests the lock to “wait a little bit,” but not give up the processor’s execution time, and see if the thread that holds the lock releases it soon. To make a thread wait, we need to make the thread perform a spin, a technique known as spin locking.

Let’s say you have two threads, T1 and T2, competing for a lock object. T1 goes in first and t2 is going to be blocked. However, it turns out that the time to execute a shared block of code is very short. If you don’t have a spin lock t2 preemption fails to block, t1 is done before the blocking is done. So T2 has to block and then wake up, which is a waste of performance. So now we just have to spin T2 and wait for T1, and if T1 finishes executing during the spin we just preempt without blocking any more.

Spin-locking is enabled by default after JDK6. The default number of spins is 10, which can be changed by using the parameter -xx :PreBlockSpin. So how many times do I spin the outside thread? More spin also has CPU performance costs, and less spin blocks the lock. So the number of spins is hard to pick.

Adaptive spin

Adaptive spin locking means that the optional time is no longer fixed, but is determined by the previous spin time on the same lock and the state of the lock owner. If the spin wait has just successfully acquired a lock on the same lock object, and the thread holding the lock is running, the virtual machine will assume that the spin is likely to succeed again, and it will allow the spin wait to last longer. In addition, if the spin is rarely successfully acquired for a lock, it is possible to omit the spin process from future attempts to acquire the lock to avoid wasting processor resources.

Synchronized usage

The usage of synchronized may be understood by everyone, generally speaking, is the object and method of locking.

Lock object

public class SynchronizedMethod1 {
    private static int i = 0;
    public void method(a){
        synchronized(SynchronizedMethod1.class){ i++; }}Copy the code

From the decomcompiled synchronized code block you can see that the synchronized block is created bymonitorenterOrder in, and thenmonitorexitRelease the lock. Before monitorenter attempts to acquire the lock, incrementing the lock counter by one if the object is not locked or if the current thread already owns the lock. When monitorexit is executed, the lock counter is also reduced by one. Blocks when the lock fails to be acquired, waiting for the lock to be released.

But why are there two Monitorexit? The second Monitorexit handles exceptions. If you look closely at the decomcompiled bytecode, the first Monitorexit normally executes the instruction that redirects to return on line 23. This means that normally only the first Monitorexit releases the lock and returns. If an exception occurs during execution, the second MonitoreXit comes into play, which is automatically generated by the compiler to handle the exception when it occurs and then release the lock.

Lock method

public class SynchronizedMethod2 {
    public synchronized void method(a) {
        System.out.println("Hello World!"); }}Copy the code

As a result of the decompression, the method synchronization is not done with the monitorenter and Monitorexit directives, but with more constant flags than normal methodsACC_SYNCHRONIZEDIdentifier. The JVM implements method synchronization based on this identifier: When the method is invoked, the calling instruction will check whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the executing thread will acquire monitor first, execute the method body after the method is successfully obtained, and release monitor after the method is executed. During method execution, the same Monitor object is no longer available to any other thread.

conclusion

There’s a lot more synchronized than I expected, and there’s not much detail on the Internet. I did my best to find articles and videos that I could understand, but there was still a lot of content I didn’t find. Next, I will continue to pay attention to the relevant information about synchronized. If there is anything gained, I will continue to add it to this article.

Standing on the shoulders of giants

  • [concurrent Java programming: Synchronized and its realization principle] www.cnblogs.com/paddix/p/53…
  • [a point about Synchronized, 99% of articles online all wrong] mp.weixin.qq.com/s/3PBGQBR9D…
  • [into Synchronized the underlying implementation] mp.weixin.qq.com/s/E8qOcBz5G…
  • For an in-depth understanding of the underlying principles of synchronized, one article is enough! Cloud.tencent.com/developer/a…