The well-known keyword Synchronize is a common solution to concurrency problems. It can be used in the following three ways:

  • 1. Synchronize normal methods and lock the current object.
  • 2. Synchronize static methods and lock the current Class object.
  • 3. Synchronize blocks. Lock objects in {}.

How it works: The JVM synchronizes methods and synchronized blocks by entering and exiting the object Monitor.

The implementation is to add a monitor.enter directive after compilation before synchronous method calls, and insert monitor.exit directives at exit methods and exceptions.

Its essence is to fetch an object Monitor, and the fetch process is exclusive so that only one thread can access it at a time.

Threads that do not acquire the lock will block at the method entry until the thread that acquired the lock, monitor.exit, attempts to acquire the lock.

The flow chart is as follows:

To demonstrate this, use a piece of code:

You can use Javap -c Synchronize to view detailed information after compilation.

You can see the Monitorenter and Monitorexit directives at the entrance and exit of the synchronized block, respectively.

Lock the optimization

Synchronize is often referred to as a weight lock. In JDK1.6, multiple optimizations for Synchronize include biased locks and lightweight locks to reduce the cost of fetching and releasing locks.

Lightweight lock

When the code enters the synchronization block, if the synchronization object is in lockless state, the current thread will create a Lock Record area in the stack frame, and copy the Mark Word in the Lock object header into the Lock Record, and then try to use CAS to update the Mark Word to the pointer to the Lock Record.

If the update succeeds, the current thread acquires the lock.

If the update fails, the JVM first checks whether the lock object’s Mark Word points to the current thread’s lock record.

If yes, it indicates that the current thread has the lock of the lock object and can directly enter the synchronization block.

If not, another thread preempts the lock. If there are multiple threads competing for a lock, the lightweight lock expands to a weight lock.

unlock

CAS is also used to unlock the lightweight lock, which attempts to replace the lock record with the Mark Word of the lock object. If the replacement succeeds, the synchronization is complete. If the replacement fails, another thread attempts to acquire the lock, and the suspended thread (which has ballooned to a weight lock) is awakened.

Lightweight locks improve performance because:

Most locks are considered uncontested throughout the synchronization cycle, so using CAS is less expensive than using mutex. But if locks are hotly contested, lightweight locks have the cost of mutual exclusion as well as the cost of CAS, and are even slower than weight locks.

Biased locking

To further reduce the cost of locking, biased locking was introduced after JDK1.6.

Biased locking is characterized by the fact that there is no multi-thread contention and the lock should be acquired multiple times by one thread.

When a thread accesses a synchronized block, the thread ID is updated to the Mark Word of the lock object using CAS. If the update succeeds, the biased lock is acquired, and the lock does not need to be acquired again each time it enters the synchronized block associated with the object lock.

Release the lock

When there is another thread to obtain the lock, holding a biased locking thread can lock is released, release when waiting for the global security point (this time without the bytecode running), then will suspend threads with biased locking, according to the lock object is locked to determine the object header Mark or Word set to do not have a lock is a lightweight lock state.

Biased locking can improve the performance of a program with synchronization but no contention, but it doesn’t help much if most of the locks in the program are contention. You can use -xx :-userBiasedLocking=false to turn off biased locking and enter light locking by default.

Other optimization

Adaptive spin

When using CAS, if the operation fails, CAS spins to try again. Since spin is CPU intensive, spinning for long periods of time wastes CPU. JDK1.6 added adaptive spin:

If the spin of a lock is rarely achieved successfully, the spin will be reduced the next time.