In Java, the synchronized keyword is used to control thread synchronization. Control synchronized code from being executed by multiple threads in a multithreaded environment.

So how exactly does synchronized do thread synchronization? And what is the process of the lock upgrade process? Let’s talk about it.

0x01 Synchronized implementation details

1.1 Java code implementation

Let’s take a look at what happens if multiple threads compete for shared resources and no action is taken:

public class TestSync implements Runnable {

    private int count = 100;

    public static void main(String[] args) {
 TestSync ts = new TestSync();  Thread t1 = new Thread(ts, Thread 1 "");  Thread t2 = new Thread(ts, Thread 2 "");  Thread t3 = new Thread(ts, "Thread 3");   t1.start();  t2.start();  t3.start();  }   @Override  public void run(a) {  while (true) {  if (count > 0) {  count--;  System.out.println(Thread.currentThread().getName() + " count = " + count);  } else {  break;  }  }  } } Copy the code

Thread 2 reduces count to 97. Thread 3 and thread 1 also do count– at some point, but the result is also 97, indicating that they did count– without knowing that another thread was also doing count.

This problem, I believe we all know that synchronized can solve.

Make the following changes to the run method:

@Override
public void run(a) {
    while (true) {
        synchronized (this) {
            if (count > 0) {
 count--;  System.out.println(Thread.currentThread().getName() + " count = " + count);  } else {  break;  }  }  } } Copy the code

Execute count– orderly, no insecurity issues.

Therefore, at the code level, adding the keyword synchronized solves the thread safety problem described above.

1.2 How to achieve synchronized at the bytecode level

The jclasslib Bytecode Viewer allows you to easily view the Bytecode execution instructions of the program using IDEA:


Let’s look at the bytecode instruction:




Synchronized is actually implemented at the bytecode level by monitorenter and Monitorexit directives, which are used to implement synchronized.

“Monitorenter” :

Java objects are naturally a Monitor, and when the Monitor is occupied, it is locked.

Each object is associated with a monitor. And the monitor is locked only if it is held by a thread.

The thread executing monitorenter attempts to take ownership of monitor:

  • If the monitor associated with the objectref has an entry count of 0, the thread enters the monitor and sets its entry count to 1. The thread is then the owner of monitor.
  • If a thread already has a monitor associated with the objectref, it will re-enter the monitor, increasing its entry count. This is lock reentrant.
  • If another thread already owns the monitor associated with the objectref, that thread will block until the count of the monitor’s entries is zero, and then try again to take ownership.

“Monitorexit” :

One or more MonitorExit directives can be used with Monitorenter directives, which collectively implement synchronization statements.

Although monitorenter and Monitorexit directives can be used to provide equivalent locking semantics, they are not used in the implementation of synchronous methods.

The JVM’s handling of monitoreXit is divided into normal exit and exception exit:

  • When the normal synchronization method completes, the monitor exit is handled by the Java Virtual machine’s return instruction. This means that the JVM has an instruction that implicitly completes the exit of monitor when the program completes its normal executionmonitorexitThe instruction isathrow.
  • If an exception occurs in a synchronized statement, so does the JVM’s exception handling mechanismmonitorexit.
Simple lock unlocking process

Therefore, the monitorenter directive is executed first after the synchronization block is executed and monitorexit directive is executed upon exit.

1.3 JVM layer implementation

public static void main(String[] args) {
    Object o = new Object();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());

    synchronized (o) {
 System.out.println(ClassLayout.parseInstance(o).toPrintable());  } } Copy the code

Execution Result:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total  java.lang.Object object internals:  OFFSET SIZE TYPE DESCRIPTION VALUE  0 4 (object header) 08 f3 7f 02 (00001000 11110011 01111111 00000010) (41939720)  4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)  8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)  12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total Copy the code

Not synchronized, the value of the object header for 01 00 00 00, add the lock, object changed the euro f3 7 f 02, synchronized will change the head of the new information object, the object header called markword in Hotspot.

An object’s Markword contains very important information, the most important of which is the synchronized lock. (Markword also contains GC information, as well as hashCode information.)

“Hotspot implements JVM markword information on 64-bit machines” :

Markword information

0x02 Lock Upgrade Process

2.1 Upgrade Process

In the early days of the JDK, the underlying implementation of synchronized was heavyweight, meaning that it went directly to the operating system to apply for a lock, which was inefficient.

The JDK later optimized synchronized locks to allow for the concept of lock escalation.

The lock upgrade process looks like this:

New -> “Bias Lock” -> “Lightweight Lock (Spin Lock)” -> “Heavyweight Lock”

Synchronized optimization is closely related to MarkWord.

The lowest three digits in Markword are used to represent the lock state, where one digit is the biased lock bit and the last two digits are the ordinary lock bit.

  1. Object o = new Object()

Lock = 0. 01 No lock

Note: If bias lock is turned on, the default is anonymous bias

  1. o.hashCode()

001 + hashcode

  1. The default synchronized (o)

00 -> Lightweight lock

By default, bias locks have a delay of 4 seconds

why? Because the JVM has its own threads started by default, there is a lot of sync code in the sync code. The sync code starts with the knowledge that there will be contention. If biased locks are used, biased locks will constantly revoke and upgrade locks, which is inefficient.

Start with BiasedLockingStartupDelay parameter Settings are biased locking (= 0, immediately start biased locking) :

-XX:BiasedLockingStartupDelay=0
Copy the code
  1. If bias locking is enabled

Lock upgrade process: New Object () -> 101 bias lock -> thread ID is 0 -> Anonymous BiasedLock

Open bias lock, new out of the object, the default is a bias anonymous object 101

  1. If a thread is locked

Upbias locking refers to the process of changing the markword thread ID to your own thread ID.

Biased lock can not be re-biased, batch biased, batch revoke

  1. If there are threads contending

Undo bias lock and upgrade to lightweight lock

A thread generates a LockRecord in its own thread stack, sets the markword as a pointer to its own thread’s LR with CAS operation, and sets the winner to get the lock

  1. If competition intensifies

Competition increases: threads that spin more than 10 times, (-xx :PreBlockSpin parameter is adjustable), or spin threads that spin more than half of the CPU cores. After JDK 1.6, the JVM controls by adding adaptive spin Self Spinning.

Upgrade heavyweight locks: Apply resources to the operating system, Linux MUtex, CPU system calls from level 3-0, thread hangs, enters a wait queue, waits for the operating system to schedule, and then maps back to user space.

To summarize, the lock upgrade process looks like this:

Lock upgrade process

2.2 Why do we need heavyweight locks when we have spin locks

Spin is a CPU drain, and if a lock is held for a long time, or if there are many spin threads, the CPU will be consumed.

Heavyweight locks have a wait queue, and those that cannot get the lock enter the wait queue without consuming CPU resources

2.3 Is bias locking necessarily more efficient than spin locking?

Not necessarily. In cases where there is a clear understanding that there will be multithreaded contention, biased locking will definitely involve lock undo, so use spin locking directly.

During JVM startup, there are many threads competing (explicitly), so by default, bias locks are not turned on at startup and will be turned on later.

2.4 Synchronized low-level implementation

At the hardware level, the lock actually executes the LOCK CMPXCHG XX instruction.

Synchronized at the bytecode level:

If a method is locked, the JVM adds a synchronized modifier;

For faster synchronization code, use the Monitorenter and Monitorexit directives.

When the JVM sees synchronized modifiers or monitorenter and monitorexit, C++ calls the synchronization mechanism provided by the operating system.

The CPU level is achieved using the LOCK instruction.

For example, synchronized memory can change the value of “I” from “0” to “1”. This process may take several instructions or cannot be synchronized (too fast) on the CPU, so a lock instruction is required.

If a lock is added before CMPXCHG, this area will be locked during the execution of subsequent instructions. Only this instruction can be modified, and other instructions cannot be operated.

0 x03 summary

  • Java object header “MarkWord”

    In the Hotspot VIRTUAL machine, objects are laid out in memory in three areas:

    • Object head
    • The instance data
    • Alignment filling

Java object headers are the basis for synchronized lock objects, which are generally stored in Java object headers. It is the key to lightweight and biased locking.

  • monitor

A synchronization tool can also be described as a synchronization mechanism.

Why can every object be a lock? Because each Java Object has a native C++ Object oop/oopDesc that corresponds to it within the JVM, and the corresponding oop/oopDesc Object has a markOop Object header, which is where the lock is stored. There’s also an ObjectMonitor, the ObjectMonitor, so that’s one of the reasons why every object can be a lock.

  • Lock upgrade (optimize) process

Synchronized locks are optimized, introducing biased locks and lightweight locks; The lock level is upgraded from low to high, with no lock -> bias lock -> Lightweight lock -> Heavyweight lock.

  • Biased locking

When a thread, when accessing a synchronized block and obtain the lock will lock in the head and stack frame object record store to lock in the thread ID, after this thread synchronization block on the entry and exit for CAS operation is not needed to lock and unlock, simply test whether object head markword in store of biased locking points to the current thread.

Open: “- XX: BiasedLockingStartupDelay = 0”

  • spinlocks

There is a limit to the spin wait time or number of spins, and if the spin exceeds the defined time and the lock is not acquired, the thread should be suspended.

-xx :+UseSpinning enabled in JDK1.6; -xx :PreBlockSpin=10; After JDK1.7, this parameter is removed and controlled by the JVM.

  • Heavyweight lock

The heavyweight Lock is implemented through the internal monitor of the object. The essence of the monitor is the Mutex Lock implementation of the underlying operating system. The switching between the threads of the operating system needs to switch from the user state to the kernel state, and the switching cost is very high.


This article was first published on the public account: “Xingbai ER”, welcome to pay attention to reading correction. Related codes have been synchronized to GitHub github.com/xblzer/Java… .