NOTE
6.24 Lock Optimization
1. JVM optimizations for locks: lock elimination, lock coarsening, biased locking, and adaptive locking. 2. Analysis of lock inflation. 3. Application-level optimization of lock usage. 4. Adjusting the number of spins.
This is a historical learning note and may contain outdated or incomplete understanding.
1. JVM Optimizations for Locks
Before JDK 1.5, the efficiency of the internal synchronized lock was very low. After 1.5, many optimizations were made to improve lock performance.
The main optimization measures are as follows.
1.1. Lock Elimination
The JIT (not javac) uses escape analysis and inlining to analyze whether a synchronized block can only be accessed by one thread.
If so, it does not generate monitor-related bytecode instructions and inlines the bytecode into the caller’s bytecode.
1.2. Lock Coarsening
The JIT merges several adjacent synchronized blocks into one larger synchronized block.
- Advantage It avoids the overhead of one thread repeatedly acquiring and releasing the same lock.
- Disadvantage The thread holds the lock for too long, causing other threads to wait longer for the lock to be released.
1.3. Biased Locking
This is based on the observation that most locks are held by at most one thread, so the CAS operation of the underlying monitor bytecode is unnecessary.
Therefore, when a lock is first held by a thread, that thread is recorded as the biased thread. When the same thread acquires it again, the lock can be given directly to it without a CAS operation.
This continues until a second thread accesses the lock.
1.4. Adaptive Locking
Thread A holds lock X. Other threads that want to acquire the lock need to wait for it to be released. There are two approaches:
- Suspend the thread, which causes a context switch.
- Busy-wait, which consumes more CPU resources.
The JVM switches between these two strategies according to the actual situation.
2. Analysis of the Lock-Inflation Process
After the JVM optimizes synchronized locks with the measures above, as multithreaded contention for the lock becomes more intense, the synchronized lock exhibits an inflation process: no lock → biased lock → lightweight lock → heavyweight lock.
A lock can be upgraded but cannot be downgraded.
2.1. No Lock
In a single-threaded case, there is no need to lock, so the lock can be eliminated. For example, using StringBuffer in a single thread.
2.2. Biased Lock
There are multiple threads, but the thread acquiring the lock each time is always the same thread. Recording it and giving the lock directly to it next time can reduce a lot of unnecessary performance overhead and context switching.
2.3. Lightweight Lock
There are multiple threads, but contention is not intense. When another thread fails to acquire the lock, it only needs to spin briefly before it can acquire it. However, the number of spins is limited; if that number is exceeded, the lock is upgraded to a heavyweight lock.
2.4. Heavyweight Lock
There are multiple threads and contention is intense, so the lock is upgraded to a heavyweight lock and threads block.
3. Optimize Lock Usage at the Application Level
As can be seen above, lock overhead is mainly reflected in lock contention. Therefore, during development, we should pay attention to reducing the degree of lock contention.
3.1. Ways to Reduce Contention
- Reduce how long the lock is held. For example, reduce the length of the critical section.
- Reduce how frequently the lock is acquired. For example, do not frequently acquire and release a lock; merge operations into one larger block when necessary.
4. Adjust the Number of Spins
The JVM provides the -XX:+UseSpinning parameter to enable spin locks and the -XX:PreBlockSpin parameter to set the number of waits for a spin lock. The default is 10 spins.
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub