Synchronized is a key word used to solve the problem of data synchronization in concurrent situations. In human terms, in multi-threaded situations, code modified by synchronized can only be executed by one thread at a time. Here’s how synchronized is used, how synchronized limits single thread execution by locking objects, and how synchronized connects to Monitor objects through object headers. Finally, we will introduce the concepts of biased locking, lightweight locking and heavyweight locking introduced after JDK1.6.

Synchronized usage

Three kinds of usage

  • Synchronizes instance methods that lock the current instance object
  • Synchronous class static method where the lock is the current class object
  • Synchronized code block, lock is the object inside parentheses
public class SynchronizedTest {

    /** * synchronizes the instance method and locks the instance object */
    public synchronized void test(a) {}Synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class synchronizedtest. class
    public synchronized static void test1(a) {}/**
     * 同步代码块
     */
    public void test2(a) {
        String str = "ABC"
        // Lock the instance object
        synchronized (str) {

        }
    }
}
Copy the code

Principle of synchronized

From the usage described above, synchronized locks are used to lock class objects or instance objects. So let’s start with the Java object memory layout.

Java object memory layout

In the HotSpot VIRTUAL machine, the layout of objects stored in memory can be divided into three areas: object headers, Instance Data, and object Padding.

Instance data: the effective information stored by the object and the attribute data information of the class, including the attribute information of the parent class;

Align padding: Because the virtual machine requires that the object’s starting address be a multiple of 8 bytes. Padding data does not have to exist, just for byte alignment.

The highlight is the next object header, which contains the answer associated with the lock.

Object header: Hotspot Vm object header contains two parts of data: Mark Word (Mark field) and Class Pointer (type Pointer).

Object head

Class Pointer: a Pointer to an object’s Class metadata that the virtual machine uses to determine which Class instance the object is

Mark Word: Used to store the runtime data of the object itself, that is, it stores key information associated with the lock.

Mark Word

Below is the Mark Word store information. Let’s focus on the weight lock line and the pointer to the mutex (weight lock) line. Other subsequent introductions. The point is to figure out what is a mutex pointer?

The monitor object

Okay, let’s move back to synchronized.

Decompile the previously generated synchronizedtest.class using the Javap command

javap -c SynchronizedTest.class
Copy the code

After decompiling, the following bytecode is obtained

Compiled from "SynchronizedTest.java" public class SynchronizedTest { public SynchronizedTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void doSth(); Code: 0: LDC #2 // SynchronizedTest 2: DUP 3: ASTore_1 4: Monitorenter getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #4 // String test Synchronized 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;) V 13: ALOAD_1 14: Monitorexit // Focus 15: GOto 23 18: Astore_2 19: ALOAD_1 20: Monitorexit // focus 21: ALOad_2 22: athrow 23: return Exception table: from to target type 5 15 18 any 18 21 18 any }Copy the code

The focus here is on the two lines monitorenter and Monitorexit.

Monitorenter: Attempts to acquire the Mark Word heavyweight lock object mentioned above. It’s actually a Monitor object.

Monitorexit: The thread that owns monitor. After executing the synchronization code, the counter decreases by 1. If the counter is 0, the thread no longer owns Monitor. Other threads are allowed to attempt to acquire the Monitor.

There are two Monitorexit because 15 goes to 23 after the normal flow. Below is the release of Monitor objects when exceptions are taken into account.

So it’s all about the monitor

A Monitor Lock is essentially implemented by relying on the underlying operating system’s Mutex Lock. Each object corresponds to a “mutex” tag, which ensures that only one thread can access the object at any one time.

Mutex: Used to protect critical sections and ensure that only one thread accesses data at a time. To access a shared resource, the mutex is first locked. If the mutex is locked, the calling thread blocks until the mutex is unlocked. After access to the shared resource is complete, the mutex is unlocked.


conclusion

The synchronized keyword locks an object — the Mark Work in the object header — the Monitor object pointed to in the Mark Word — a Mutex Lock on the corresponding operating system.

Finally achieve the operating system mutex association. Now let’s move on to the data structure of the Monitor object

The source code for monitor is written in C++ and stored in the objectmonitor. HPP file of the vm. The data structure looks like this

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // Number of records
    _waiters      = 0,
    _recursions   = 0; // Lock reentrant times
    _object       = NULL;
    _owner        = NULL;  // Owns the thread that holds ObjectMonitor
    _WaitSet      = NULL;  // Threads in wait state are added to _WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // Threads in the waiting block state are added to the list
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
Copy the code

Explain the difference between a thread state of Waiting and Blocked. Synchronized causes the thread to enter the Blocked state, object.wait () causes the thread to enter the Waiting state, and the Waiting thread will also enter the Blocked state when reacquiring the lock on the Object after being awakened by another thread calling Object.notify(). In other words, the Waiting thread gave up the time slice, while the Blocking thread wanted to own the time slice, but did not acquire the lock.

Lock the optimization

Locking is optimized in JDK1.6, introducing the concepts of biased locking and lightweight locking. Here are the two types of locks.

Biased locking

HotSpot authors have found that in most cases locks are not only not contested by multiple threads, but are always acquired multiple times by the same thread. Hence the name biased lock. Locks are upgraded to lightweight locks when thread contention occurs.

Bias lock acquisition process

  1. Visit the Mark Word(refer to the above figure for specific location) to see whether the bias lock identifier is set to 1 and whether the lock flag bit is 01 — confirm that it is in the bias state.
  2. If yes, test whether the thread ID points to the current thread. If yes, go to step (5); otherwise, go to Step (3).
  3. If the thread ID does not point to the current thread, the lock is contested through the CAS operation. If the pointer is empty and the competition succeeds, set the thread ID in Mark Word to the current thread ID, and then execute (5); If the competition fails, perform (4).
  4. If the CAS fails to acquire the biased lock, it indicates that there is a contention. (The CAS fails to acquire the biased lock means that at least some other thread has acquired the biased lock, because the thread does not actively release the biased lock.)
  5. Execute synchronization code.

After losing the competition

Biased lock Only when other threads attempt to compete for biased lock, the thread holding biased lock will release the lock, the thread will not actively release biased lock. A partial lock is revoked by waiting for the global safe point (at which no bytecode is being executed), which first suspends the thread with the partial lock to determine whether the thread with the lock is active. If hung, the bias lock is revoked and reverts to lockless (flag bit “01”), then bias the new thread again, and if still alive, upgrades to lightweight lock (flag bit “00”).

The general flow chart is as follows

Biased locking is enabled by default in JDK 6 and later JVMS. Biased locking can be turned off by JVM arguments: -xx: -usebiasedLocking =false. After this is turned off, the program enters the lightweight locking state by default.

Lightweight lock

When thread contention is not serious, other threads will try to acquire the lock in the form of spin without blocking, thus improving performance.

Lightweight Lock upgrade

If there is currently only one waiting thread, the thread waits through spin. But when the spin exceeds a certain number, or when one thread is holding the lock, one is spinning, and a third person is calling, the lightweight lock is upgraded to the heavyweight lock.

Acquisition process

  1. Check whether the lock status of the synchronization object is lockless (lock flag bit is “01”, whether it is biased lock is “0”). If yes, go to Step (2); otherwise, go to Step (5).
  2. The virtual machine will first establish a space called Lock Record in the stack frame of the current thread, which is used to store the copy of the current Mark Word of the Lock object, officially known as the product of the product.
  3. Copy the Mark Word from the copy object header to the lock record.
  4. After the copy is successful, the VM uses the CAS operation to change the Mark Word of the object to the pointer to the lock record, and the owner pointer in the lock record to the Object Mark Word. If the update succeeds, the lock is contested, and the synchronization code is executed; otherwise, step (5) is performed.
  5. If mark is currently locked and the PTR pointer in MARK points to the stack frame of the current thread, the synchronization code is executed. Otherwise, multiple threads are competing for the lightweight lock. If only one thread is currently waiting, spin to wait for a while, and another thread may release the lock soon. But when the spin exceeds a certain number, or when one thread is holding the lock, one is spinning, and a third person is calling, the lightweight lock expands to the heavyweight lock.

The flow chart

The difference between

The main difference between a lightweight Lock and a partial Lock is the Lock Record, and the lightweight Lock allows a slight contention.

conclusion

Example: Synchronize locks an object to Synchronize a block of code, and the transition between no-lock > biased > lightweight > heavyweight locks. See how ReentrantLock implements locking later if you have time.

The resources

The Art of Concurrent Programming in Java

Answered by Zhihu user EnjoyMoving

The unspoken Java “lock” article from the Meituan technical team

Synchronized implementation of JVM source analysis

Synchronized parsing — if you’d like to peel me open layer by layer

Synchronized low-level implementation