An overview of synchronized
1.1 Features of synchronized
Using locks can achieve data security, but it will cause performance degradation. Synchronized is a heavyweight lock, and the lock upgrade process is:No lock -> Biased lock -> Lightweight lock -> Heavyweight lock.
When concurrency is high, synchronization calls should try to consider the performance loss of locks. If you can use lock-free data structures, don’t use locks. If you can use blocks, don’t lock the entire method body. If you can have object locks, don’t use classes. Lock.
1.2 Reasons for the inefficiency of synchronized lock performance
In early versions of Java, synchronized is a heavyweight lock and is inefficient, because the monitor relies on the Mutex Lock (system mutex) of the underlying operating system strong>. Suspending and waking up threads needs switching between user mode and kernel, and the time cost is relatively high. After Java 6, in order to reduce the performance consumption caused by acquiring and releasing locks, lightweight locks and biased locks were introduced.
1.3 Monitor function
Monitor is a synchronization tool and a synchronization mechanism, which can be described as a Java object. Java objects are born monitors. Every Java object has the potential to become a monitor, because in the design of Java , every java object has an invisible lock with it since it was born. It is called an internal monitor lock.
1.4 The role of ObjectMonitor
Synchronization in jvm is based on entering and exiting monitor objects. Each object instance will have a monitor, and the monitor can be created and destroyed together with the object. Among them, moniior is implemented by objectmonitor, and objectmonitor is implemented by the objectmonitor.hpp file of c++.
1.5 synchronized upgrade process
synchronized uses locks and is stored in the mark down field in the header of the java object. Lock upgrade mainly depends on the lock flag and release bias lock flag in markdown
1. Biased lock: markdown stores the biased thread ID;
2. Lightweight lock: markdown stores a pointer to the Lock Record in the thread stack.
3. Weight lock: markdown stores pointers to monitor objects in the heap
When a synchronized method is locked by a thread, the object where the method is located will bias the lock modification status bit in the mark word where it is located, and will also occupy the first 54 bits to store the thread pointer as an identifier. If the thread accesses the same synchronized method again, the thread only needs to go to the mark word of the object header to determine whether there is a biased lock pointing to its own ID, and there is no need to enter the monitor to compete for the object.
1.6 Summary and comparison
1 Bias lock: Suitable for single-threaded situations. When entering a synchronization method/code block when there is no lock competition, use bias lock.
2. Lightweight lock: Suitable for situations where competition is less intense. When competition exists, it is upgraded to a lightweight lock. The lightweight lock uses a spin lock. If the execution time of the synchronization method/code block is very short, use Although lightweight locks occupy a lot of CPU resources, they are still more efficient than using heavyweight locks.
3. Heavyweight lock: Used in highly competitive situations. If the synchronization method/code block takes a long time to execute, then the performance consumption caused by using lightweight lock spin will be more serious than using heavyweight lock.
At this time, you need to upgrade to a heavyweight lock.
The synchronized lock upgrade process: spin first, and then block if it fails.
1.7 The relationship between monitor and java object
The relationship between monitor and java objects
1. If a Java object is locked by a thread, the lockword in the markword field of the Java object points to the starting address of the monitor.
2. The owner field of the monitor will store the thread ID that owns the associated object lock.
3. The switching of mutex lock (mutex) requires switching from user mode to kernel mode, so the state transition takes a lot of processor time.
1.8 Lock upgrade process
1) When there is only one thread competing for the lock, the biased lock will be used first, which is to give a mark indicating that the lock is currently occupied by thread a.
2) Later, thread b and thread c came, saying why do you hold the lock and fair competition is needed, so the logo was removed, that is, the biased lock was revoked, and it was upgraded to a lightweight lock. The three threads were rotated through CAS. Lock competition (in fact, this lock competition process is still biased towards the original thread holding the biased lock).
3) Now thread a occupies the lock, thread b, and thread c have been trying to acquire the lock in a loop. Later, ten more threads came and kept spinning. Waiting like this consumes CPU resources, so the lock is upgraded to Heavyweight locks apply for resources from the kernel and directly block waiting threads.
Two lock-free status
2.1 Introduction
Lock-free state, After an object is instantiated, if it has not been competed for the lock by any thread, then it is in the lock-free state (001)
1. Structure: The first two lines are 8 bytes; the latter line is 4 bytes for the type pointer.
2. Reading process
3. Conclusion: Lock-free state, After an object is instantiated, if it has not been competed for a lock by any thread, then it is in a lock-free state (001)
Three bias locks
3.1 Biased lock concept
When a piece of synchronized code has been accessed multiple times by the same thread, since there is only one thread, the thread will automatically acquire the lock on subsequent accesses. It is designed to improve performance when only one thread performs synchronization.
3.2 Code case simulation
1.ticket
package com.ljf.haha; /** * @ClassName: Ticket * @Description: TODO * @Author: admin * @Date: 2023/09/17 07:14:52 * @Version: V1.0 **/ public class Ticket { private int num=50; Object lockObj=new Object(); public void sale(){ synchronized (lockObj){ if(num>0){ System.out.println("" + Thread.currentThread().getName() + "Sell:" + num-- + "tickets left:" + num); } } } }
2. Test
package com.ljf.haha; /** * @ClassName: Test * @Description: TODO * @Author: admin * @Date: 2023/09/17 07:15:00 * @Version: V1.0 **/ public class Test { public static void main(String[] args) { Ticket ticket=new Ticket(); new Thread(new Runnable() { @Override public void run() { for(int k=0;k<55;k + + ){ ticket.sale(); } } },"A").start(); new Thread(new Runnable() { @Override public void run() { for(int k=0;k<50;k + + ){ ticket.sale(); } } },"B").start(); new Thread(new Runnable() { @Override public void run() { for(int k=0;k<50;k + + ){ ticket.sale(); } } },"C").start(); System.out.println("----"); } }
2. Results
3.3 Bias lock locking process
The lock is always owned by the first thread that occupies it. This thread is the biased thread of the lock.
When the lock is owned for the first time, the biased thread ID is recorded, so that the biased thread always holds the lock (When the thread subsequently enters and exits this code block with a synchronization lock, There is no need to lock and release the lock again),but it will directly check whether the markword of the lock contains its own thread id.
1. If they are equal, it means that the biased lock is biased towards the current thread, and there is no need to try to obtain the lock again. The lock will not be released until competition occurs. For each subsequent synchronization, check whether the lock’s biased thread ID is consistent with the current thread ID. If they are consistent, synchronization will be entered directly. There is no need to go to cas to update the object header every time you lock or unlock. If there is only one thread using the lock from beginning to end, it is obvious that the biased lock has almost no additional overhead and the performance is extremely high.
2. If it does not wait, it means that competition has occurred and the lock is no longer always biased towards the same thread. At this time, try to use cas to replace the thread id in the markword with the new thread id.
2.1 If the competition is successful, it means that the previous thread no longer exists. The thread ID in the markword is the new thread ID. The lock will not be upgraded and is still a biased lock.
2.2 Competition fails. At this time, it may be necessary to upgradeto a lightweight lock to ensure fair competition for locks between threads.
Note: The bias lock will only release the lock when other threads try to compete for the bias lock. The thread will not actively release the bias lock.
When a synchronized method is grabbed the lock by a thread, the object where this method is located will modify the status bit of the biased lock in the mark word where it is located, and will also occupy the first 54 bit to store the thread pointer as an identity. If the thread accesses the same synchronized method again, the thread just needs to go to the mark word of the object header to determine whether there is a biased lock pointing to its own id. There is no need to enter the monitor< /strong>Competition for this object.
3.4 Case description of bias lock locking process
If a thread executes a synchronized code block, jvm uses the cas operation to record the thread pointer id into the mark word, and modifies the bias flag to indicate that the current thread has obtained the lock. The lockobject becomes a biased lock (modify the lock flag in the object header through cas). The literal meaning can be understood as a lock that is “biased towards the first thread to obtain it”. After executing the synchronized code block, the thread will not actively release the biased lock. At this time, the thread has obtained the lock and can execute the synchronized code block.
When the thread executes the synchronization code block for the second time, it will judge whether the thread holding the lock at this time is still itself (the ID of the thread holding the lock is also in the object header). The jvm passes the account object markword It is judged that the current thread ID is still there, indicating that the lock of this object is still held, and the work of the critical section can be continued. Since the lock has not been released before, there is no need to re-lock here.
Features: Only one thread is used from beginning to end. It is obvious that there is almost no additional overhead in biased locking, and the performance is extremely high.
Conclusion: JVM does not need to negotiate with the operating system to set mutex. It only needs to record the thread ID to indicate that it has obtained the current lock, without operating system access.
Biased lock: When there is no competition from other threads, the current thread is always biased, and the current thread can always execute.
3.5 Opening and closing the bias lock
-XX: + UserBiasedLocking turns on biased locking by default
-XX:-UserBiasedLocking turns off biased locking and will jump to lightweight locking
3.6 Revocation of biased lock
Biased locks use a mechanism that waits until competition occurs before releasing the lock. Only when other threads compete for the lock, the original thread holding the biased lock will be revoked. Undoing requires waiting for a safe point (no bytecode is executing at this point in time) and checking whether the thread holding the biased lock is still executing.
1. The first thread is executing the synchronized method (in a synchronized block). It has not finished executing. If other threads come to snatch it, the biased lock will be canceled and a lock upgrade will occur. At this time, the lightweight lock is held by the thread that originally held the bias lock and continues to execute its synchronization code, while the competing threads will spin and wait to acquire the lightweight lock.
2. When the first thread completes the synchronized iteration (exits the synchronized block), it sets the object header to a lock-free state, cancels the bias lock, and re-biases.
3.7 Conclusion
After java15, bias locking was abandoned.
Four lightweight locks
4.1 Lightweight Lock
Lightweight locks: Provide performance when threads execute synchronized blocks nearly alternately.
Purpose: Under the premise of no multi-thread competition, use cas to reduce the performance consumption caused by heavyweight locks using operating system mutexes. To put it bluntly, spin first and upgrade if it fails.
Upgrade timing: When the bias lock function is turned off or multi-threads compete for the bias lock, the bias lock will be upgraded to a lightweight lock.
4.2 Case Process
Assume that thread A has already obtained the lock, and then thread B comes to grab the lock of the object. Since the lock of the object has been obtained by thread A, the current lock is already a biased lock.
When thread B returns the object header mark word during contention, the thread id in the object header mark word is not thread b’s own thread id (while thread A), then B will enter the cas operation hoping to obtain the lock.
At this time, the operation of thread b is divided into two situations:
1. Obtain successfully, directly replace the thread id in the mark word, change it to b’s own id (A->B), and re-bias to other threads (that is, hand the biased lock to other threads, which is equivalent to the current thread releasing the lock) , the lock will remain in the biased lock state, with thread A Over and thread B on top. As shown below:
2. If the acquisition fails, the biased lock is upgraded to a lightweight lock (set the biased lock flag to 0 and set the flag bit to 00). At this time, the lightweight lock is held by the thread that originally held the biased lock, and its synchronization continues. code, while the competing thread B will enter the spin waiting to obtain the lightweight lock. As shown below:
4.3 Lightweight locking
1) Locking: JVM will create a space for storing lock records for each thread in the stack frame of the current thread. If a thread obtains a lock and finds a lightweight lock, it will The markword is copied to its own displaced mark word, and then the thread tries to use cas to replace the lock’s markword with the lock’s pointer to the lock record.
If it succeeds, the current thread acquires the lock; if it fails, it means that the mark word has been replaced by the lock record of other threads, indicating that it is competing with other threads for the lock, and the current thread tries to use spin to acquire the lock.
Spin cas: Keep trying to acquire the lock, don’t move up the barrel if you can’t upgrade, and try not to block.
2) Release of lightweight lock
When releasing the lock, the current thread will use the cas operation to copy the contents of the displaced mark word back to the locked mark word. If no competition occurs, the copy operation will succeed. If other threads spin multiple times and cause a slight crash, If the magnitude lock is upgraded to a heavyweight lock, the cas operation will fail. At this time, the lock will be released and the blocked thread will be awakened.
Five Weight Lock
5.1 Heavyweight Lock
The synchronized heavyweight lock in Java is implemented based on entering and exiting monitor write instances. During compilation, the monitor enter instruction is inserted at the beginning of the synchronization block, and the monitor exit instruction is inserted at the end.
When the thread executes the monitor enter command, it will try to obtain the ownership of the monitor corresponding to the object. If it is obtained, the lock is obtained, and the ID of the current thread will be stored in the owner of the monitor, so that it will be in a locked state unless synchronization is exited. block, otherwise other threads cannot obtain this monitor.
Six Summary
6.1 Lightweight locks and heavyweight locks
1. Lightweight locks need to release the lock every time they exit the synchronized block, while biased locks only release the lock when competition occurs.
6.2 Relationship between lock upgrade and hashcode
1. In the lock-free state: markword can store the value of the identity hash code of the object. When the hashcode method is called for the first time, the jvm will generate the corresponding identity hashcode value and store the changed value in markword.
2. For biased locks: When a thread acquires a biased lock, the location of the identity hash code will be overwritten with the Thread ID and epoch value. If an object’s hashcode method has been called once, the object cannot have a bias lock set. Because if it can be optimized, the identity hash code in the mark word will inevitably be biased toward the thread ID for overwriting. This will cause inconsistent results from calling the hashcode method twice on the same object.
3. Upgrade to a lightweight lock: JVM will create a lock record (LOCK Record) space in the stack frame of the current thread to store the mark word copy of the lock object. This copy can contain the identity hash code, so it is lightweight The level lock can coexist with the identity hash code. The hash code and GC age are naturally stored here. Releasing the lock will write this information back to the object header.
4. Upgrade to a heavyweight lock: The heavyweight lock pointer saved by the mark word. In the ObjectMoinitor class that represents the heavyweight lock, there is a field to record the mark word in the non-locked state. After the lock is released, the information will also be written back to the object header. .