What is a Synchronized

Synchronized is a lightweight synchronization lock provided by the JDK.

 / / lock
 synchronized(this) {
     // Business logic, internal thread safety
 }
Copy the code

Principle of synchronized

Java Source hierarchy

synchronized(o) {}
Copy the code

O represents the lock object. The MarkWord field in the object header in the object structure stores the runtime data, including the lock status information.

Bytecode hierarchy

Object locks (non-statically synchronized methods OR code blocks) : Monitorenter and Monitorexit keywords are placed before and after the method

Static lock (static method with synchronized) : class-level lock, keyword ACC_SYNCHRONIZED

The JVM level

synchronized.cpp

Why does Synchronized achieve thread synchronization?

The Hotspot object header contains two main pieces of data:

  • Mark Word (Mark field)

Mark Word: Default store object HashCode, generational age, and lock flag bit information. This information is irrelevant to the definition of the object itself, so Mark Word is designed as a non-fixed data structure to store as much data as possible in a very small amount of memory. It reuses its own storage space based on the state of the object, which means that the data stored in Mark Word changes as the lock flag bit changes during runtime.

  • Klass Pointer

Object is a pointer to its class metadata that the virtual machine uses to determine which class instance the object is.

Monitor

Monitor can be understood as a synchronization tool or a synchronization mechanism and is often described as an object. Each Java object has an invisible lock, called an internal lock or Monitor lock.

Monitor is a thread-private data structure, and each thread has a list of available Monitor Records, as well as a global list of available records. Each locked object is associated with a Monitor, and an Owner field in the monitor stores the unique identity of the thread that owns the lock, indicating that the lock is occupied by the thread.

Synchronized implements thread synchronization through Monitor, which relies on the underlying operating system’s Mutex Lock to implement thread synchronization.

Lock escalation

There are four lock states, from lowest to highest: no lock, biased lock, lightweight lock, and heavyweight lock. The lock status can only be upgraded, not degraded.

Mark Word content corresponding to the four lock states, and then explain the ideas and characteristics of the four lock states respectively:

The lock state Store content Store content
unlocked Object hashCode, object generation age, whether biased lock (0) 01
Biased locking Biased thread ID, biased timestamp, object generation age, whether biased lock (1) 01
Lightweight lock Pointer to the lock record in the stack 00
Heavyweight lock A pointer to a mutex (heavyweight lock) 10

unlocked

None Lock No resource is locked. All threads can access and modify the same resource, but only one thread can modify the resource successfully.

The lockless feature is that the modification operation occurs in a loop, and the thread constantly tries to modify the shared resource. If there are no conflicts, the modification succeeds and exits, otherwise the loop continues. If multiple threads modify the same value, one thread will succeed, and the others will retry until the modification succeeds.

CAS principle and application is the realization of no lock. Lockless can’t completely replace locking, but the performance of locking is very high in some cases.

Biased locking

Biased locking means that a piece of synchronized code is always accessed by a thread, so the thread will automatically acquire the lock, reducing the cost of acquiring the lock.

In most cases, the lock is always acquired multiple times by the same thread, and there is no multithreading competition, so biased locking occurs. The goal is to improve performance when only one thread executes a synchronized code block.

  • When a thread accesses a block of synchronized code and acquires a lock, the thread ID of the lock bias is stored in Mark Word.
  • Instead of using CAS to lock and unlock a thread when it enters and exits a synchronized block, it checks whether the Mark Word stores a biased lock pointing to the current thread.

purpose

Biased locks are introduced to minimize unnecessary lightweight lock execution paths without multithreading competition, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions, while biased locks only need to rely on one CAS atomic instruction when replacing ThreadID.

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 lock object is locked. After the biased lock is revoked, the system restores to the no-lock (flag bit is “01”) or lightweight lock (flag bit is “00”) state.

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

It means that when a biased lock is accessed by another thread, the biased lock will be upgraded to a lightweight lock. Other threads will try to acquire the lock through the form of spin without blocking, thus improving performance.

When the code enters the synchronization block, if the Lock status of the synchronization object is lockless (the Lock flag bit is “01”, whether the bias Lock is “0”), the VIRTUAL machine will first establish a space named 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. Then copy the Mark Word from the object header into the lock record.

After the copy is successful, the VM attempts to update the Mark Word of the object to a pointer to the Lock Record using the CAS operation, and sets the owner pointer in the Lock Record to the Mark Word of the object.

If the update succeeds, the thread owns the lock on the object, and the object’s Mark Word lock bit is set to 00, indicating that the object is in a lightweight locked state.

If the lightweight lock fails to update, the virtual machine first checks whether the object’s Mark Word refers to the current thread’s stack frame. If it does, the current thread already owns the lock and can proceed directly to the synchronization block. Otherwise, multiple threads compete for the lock.

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.

After 10 times, upgrade to heavyweight lock

Heavyweight lock

When upgrading to a heavyweight lock, the status value of the lock flag changes to “10”, then the pointer to the heavyweight lock is stored in the Mark Word, and all the threads waiting for the lock will enter the blocking state.

To sum up

  • Biased locking Compares Mark Word to solve the locking problem, avoiding CAS operation.
  • Lightweight locking uses CAS operations and spin to solve locking problems, preventing thread blocking and waking up from affecting performance. – Heavyweight locks block all threads except the one that owns the lock.

additional

Synchronized cannot use String Integer Long as the object of a lock

Heavyweight locks versus spinlocks

  • Short execution time, small number of threads using spin
  • Long execution time, the number of threads using system locks