Http://www.infoq.com/cn/articles/java-se-16-synchronized
1 Introduction
In multi-threaded concurrent programming, synchronized has always been an elder role, and many people will call it a heavyweight lock, but with the various optimizations that Java SE1.6 has made to synchronized, in some cases it is not so heavy, this article details the Java The biased and lightweight locks introduced in SE1.6 to reduce the performance cost of acquiring lock and release locks, as well as the storage structure and upgrade process of locks.
2 Terminology Definitions
Cas |
Compare and Swap |
Compare and set. Used to provide atomic operations at the hardware level. In Intel processors, comparisons and exchanges are implemented via the instruction Cmpxchg. Comparisons are consistent with the given values, and if they are consistent, they are not modified. |
3 Basics of synchronization
Each object in Java can be used as a lock.
- For synchronous methods, the lock is the current instance object.
- For a static synchronization method, the lock is the class object of the current object.
- For synchronous method blocks, the lock is an object configured in synchonized brackets.
When a thread tries to access a synchronous block of code, it must first get a lock, exit or throw an exception when it must release the lock. So where does the lock exist? What information is stored inside the lock?
4 Principle of synchronization
The JVM specification specifies that the JVM implements method synchronization and code block synchronization based on entering and exiting the monitor object, but the implementation details are different. Code block synchronization is implemented using Monitorenter and monitorexit directives, and method synchronization is implemented in another way, details are not explained in detail in the JVM specification, but synchronization of methods can also be implemented using these two instructions. The monitorenter instruction is inserted at the beginning of the synchronization code block after compilation, and Monitorexit is inserted at the end of the method and at the exception where the JVM guarantees that each monitorenter must have corresponding monitorexit paired with it. Any object has a monitor associated with it, and when a monitor is held, it is locked. When the thread executes to the monitorenter instruction, it attempts to take ownership of the monitor that corresponds to the object, which is attempting to acquire the lock on the object.
4.1 Java Object Header
The lock exists in the Java object header. If the object is an array type, the virtual machine stores the object header with 3 word widths, and if the object is a non-array type, the object header is stored with a 2-word width. In a 32-bit virtual machine, the word width equals four bytes, or 32bit.
Length |
Content |
Description |
32/64bit |
Mark Word |
Stores the object's hashcode or lock information, and so on. |
32/64bit |
Class Metadata Address |
Pointer to data stored in object type |
32/64bit |
Array length |
The length of the array (if the current object is an array) |
In the Java object header, Mark Word stores the object's hashcode, generational age, and lock tag bits. The default storage structure for the 32-bit JVM's Mark Word is as follows:
|
-bit |
4bit |
1bit Whether the lock is biased |
2bit Lock Flag bit |
No lock status |
Hashcode of objects |
Age of Object generation |
0 |
01 |
The data stored in Mark Word during Operation changes as the lock flag bit changes. Mark Word may change to store the following 4 kinds of data:
Lock status |
-bit |
4bit |
1bit |
2bit |
23bit |
2bit |
Whether the lock is biased |
Lock Flag bit |
Lightweight lock |
Pointer to lock record in stack |
00 |
Heavy-weight lock |
Pointer to mutex (heavyweight lock) |
10 |
GC Flag |
Empty |
11 |
Biased lock |
Thread ID |
Epoch |
Age of Object generation |
1 |
01 |
Under 64-bit virtual machines, Mark Word is 64bit in size and has the following storage structure:
Lock status |
25bit |
31bit |
1bit |
4bit |
1bit |
2bit |
|
|
Cms_free |
Age of Generation |
Biased lock |
Lock Flag bit |
No lock |
Unused |
Hashcode |
|
|
0 |
01 |
Biased lock |
ThreadID (54bit) Epoch (2bit) |
|
|
1 |
01 |
4.2 Upgrade of the lock
In order to reduce the performance cost of acquiring locks and unlocking locks, Java SE1.6 introduces "biased lock" and "lightweight lock", so there are four states in the Java SE1.6 Lock, no lock state, favor lock state, lightweight lock state and heavy lock state, it will gradually escalate with the competition situation. Locks can be upgraded but not degraded, which means that a biased lock cannot be downgraded to a biased lock after it has been upgraded to a lightweight lock. This lock escalation policy is not degraded, and is designed to improve the efficiency of lock and release locks, which are analyzed in detail below.
4.3 Bias lock
The authors of the hotspot have found that in most cases, the lock is not only a multi-threaded competition, but is always obtained by the same thread multiple times, and a biased lock is introduced in order to get the lock to a lower cost. When a thread accesses a synchronization block and acquires a lock, the thread ID of the lock bias is stored in the lock record in the object header and the stack frame, and the thread does not need to spend the CAS operation to lock and unlock when it enters and exits the synchronization block, but simply tests the object's head's mark Whether Word stores a biased lock pointing to the current thread, if the test succeeds, indicates that the thread has acquired a lock, and if the test fails, it needs to be tested to see if the identity of the biased lock in Mark Word is set to 1 (indicating that it is currently biased), if not set, the CAS competition lock is used, if set, Attempts to use CAs to point a biased lock on the object header to the current thread.
Reverse Lock: Biased locking uses a mechanism that waits until the competition appears to release the lock, so the thread that holds the biased lock releases the lock when other threads try to compete for a bias lock. A bias lock revocation, which waits for a global security point (at which no bytecode is executing), pauses the thread that holds the biased lock first, and then checks to see if the thread holding the biased lock is alive, and if the thread is not active, the object header is set to a lock-free State, and if the thread is still alive, A stack with a biased lock is executed, traversing the lock record of the biased object, the lock record in the stack, and the object header's mark Word either re-biased to another thread, or reverts to a lock-free or tagged object that does not fit as a bias, and finally wakes up the suspended thread. Thread 1 in is a process that favors lock initialization, and thread 2 demonstrates the process of favoring lock revocation.
Turn off bias Lock: The bias lock is enabled by default in Java 6 and Java 7, but it is activated only a few seconds after the application starts, and if necessary, the JVM parameter can be used to turn off delay-xx:biasedlockingstartupdelay = 0. If you are sure that all the locks in your application are normally in a competitive state, you can turn off the biased lock-xx:-usebiasedlocking=false by using the JVM parameter, then the default is to enter the lightweight lock state.
4.4 Lightweight Lock
Lightweight Lock-Lock: Before the thread executes the synchronization block, the JVM creates space in the stack frame of the current thread to store the lock record and copies Mark word in the object header to the lock record, officially known as displaced Mark Word. The thread then attempts to replace Mark Word in the object header with a pointer to the lock record using CAs. If successful, the current thread acquires the lock and, if it fails, indicates that another thread is competing for the lock, and the current thread attempts to use the spin to acquire the lock.
Lightweight lock Unlock: When lightweight unlocked, atomic CAS operations are used to replace displaced Mark word back to the object header, and if successful, it means no contention occurs. If it fails, indicating that the current lock is competing, the lock expands into a heavyweight lock. is a flowchart where two threads compete for locks at the same time, resulting in lock expansion.
Since spin consumes the CPU, in order to avoid useless spins (for example, the thread that gets the lock is blocked), once the lock is upgraded to a heavyweight lock, it is no longer restored to the lightweight lock state. When the lock is in this state, the other threads are blocked when they attempt to acquire the lock, and when the lock-holding thread releases the lock, the threads are awakened, and the awakened thread makes a new round of lock contention.
5 advantages and disadvantages of the lock comparison
Lock |
Advantages |
Disadvantages |
Applicable scenarios |
Biased lock |
Locking and unlocking does not require additional consumption, and the execution of a non-synchronous method is less than the nanosecond-level gap. |
If there is a lock contention between threads, additional lock revocation consumption is brought. |
Applies to only one thread to access the synchronization block scenario. |
Lightweight lock |
The competing threads do not block, increasing the responsiveness of the program. |
If a thread that does not have a lock contention is always using spin, it consumes the CPU. |
The pursuit of response time. Synchronization blocks execute very quickly. |
Heavy-weight lock |
Thread contention does not use spin and does not consume CPU. |
The thread is blocked and the response time is slow. |
Pursuit of throughput. The synchronization block executes more slowly. |
6 reference source
Some of the contents of this article refer to Hotspot source code. Object Head Source code MARKOOP.HPP. Biased lock source biasedLocking.cpp. As well as other source ObjectMonitor.cpp and BasicLock.cpp.
Synchronized Lock Spin 2