In-depth understanding of various locks in Java (on)

I don’t know if you have been confused by the various locks in Java.

What is the difference between lightweight locks, heavyweight locks, fair locks, unfair locks, lock locks and synchronized locks? First look at the pictures and then say one by one:

image


1. Pessimistic lock VS optimistic lock


Pessimistic lock: For concurrent operations of the same data, pessimistic lock thinks that when I modify the data, other threads will definitely come in to modify the data, so I need to add a lock to prevent other threads from entering when I modify the data. The locks implemented by this idea are pessimistic locks; in Java, synchronized and ReentrantLock are both pessimistic locks.


Optimistic locking: For concurrent operations on the same data, optimistic locking believes that no other threads will modify the data when modifying the data, so no locks will be added to restrict other threads from entering, only when the data is updated Determine if any thread has updated the data before. If the data has not been updated, then the current thread has the right to update the data. If it is updated, try again.


image


According to the description of this picture, a conclusion can be drawn:


Pessimistic lock is suitable for scenarios with many write operations. Locking first can ensure that the data is correct during write operations.

Optimistic lock is suitable for scenarios with many read operations, and the feature of no lock can greatly improve the performance of read operations.


Optimistic locking is also called lock-free programming in Java. A typical implementation of jdk is CAS.

CAS is the cornerstone of the implementation of the entire JUC toolkit.

Those who need to know more about CAS click here: CAS must know and know


Specific implementations of pessimistic lock include  synchronized and  ReentrantLock,  etc., which will be mentioned later.



2. Spin lock VS adaptive spin lock



In the operating system  blocking  or  wake up  a Java operating system thread needs  to switch CPU state  is accomplished, the state transition requires  takes processor time  . If the content in the synchronization code block is too simple, the time consumed by the state transition may be longer than the execution time of the user code.


Spin lock : In many scenarios, the lock time of synchronization resources is very short. For this short period of time to switch threads, the cost of thread suspension and restoration of the scene may make the system more expensive. If it is a multi-core CPU that allows multiple threads to execute concurrently at the same time, we can make the latter thread requesting the lock not give up the execution time of the CPU, and see if the thread holding the lock will release the lock soon.

In order to make the current thread "wait a minute", we need to spin the current thread. If the thread that locked the synchronization resource before the spin has released the lock, then the current thread can directly acquire the synchronization resource without blocking. , Thereby avoiding the overhead of switching threads. This is the spin lock.


The spin lock itself has disadvantages, and it cannot replace blocking. Although spin waiting avoids the overhead of thread switching, it takes up processor time. If the lock is occupied for a short time, the effect of spin waiting will be very good. Conversely, if the lock is occupied for a long time, the spinning thread will only waste processor resources. Therefore, the spin waiting time must have a certain limit. If the spin exceeds the limited number of times (the default is 10 times, you can use -XX:PreBlockSpin to change) and the lock is not successfully obtained, the thread should be suspended.


Spin lock was introduced in JDK1.4.2, use -XX:+UseSpinning to open. In JDK 6, it is turned on by default, and an adaptive spin lock (adaptive spin lock) is introduced.


Adaptive spin lock : Adaptive means that the time (number of times) of the spin is no longer fixed, but is determined by the previous spin time on the same lock and the state of the lock owner. If on the same lock object, spin waiting has just successfully obtained the lock, and the thread holding the lock is running, then the virtual machine will think that this spin is also likely to succeed again, and then it will allow the spin The wait lasts for a relatively longer time. If the spin is rarely successfully obtained for a certain lock, it is possible to omit the spin process when trying to acquire the lock in the future, and directly block the thread to avoid wasting processor resources.


3. Bias lock VS lightweight lock VS heavyweight lock


These three locks are  the three manifestations of the  synchronized keyword. Before understanding these three lock states, let's take a look at what  Java object headers  and  monitors are  .


The object header is mainly composed of two parts: Mark Word and instance data. Here we need to focus on the description

The role of Mark Word is closely related to the lock state. First look at the two pictures


image.png


image.png


Mark Word : mainly composed of five parts


hashcode

Thread ID (whether the thread has acquired the lock)

Epoch (Save bias timestamp)

Object generational age (the object has gone through several garbage collections in the heap space)

Is it a biased lock  (0 is not, 1 is)

Lock flag  (00 light weight lock 10 heavy weight lock 01 bias lock)



Monitor

Monitor can be understood as a synchronization tool or a synchronization mechanism, usually described as an object. Every Java object has an invisible lock, called an internal lock or a monitor lock.

Synchronized thread synchronization is achieved through the Monitor, which relies on the Mutex Lock (mutual exclusion lock) of the underlying operating system to achieve thread synchronization.

 After the synchronized keyword is added to the method , the object must get the Monitor to be considered as getting the lock. If it does not get the lock, it will enter a  synchronized Qreue queue and block.



image.png



As mentioned in the spin lock before, "Blocking or waking up a Java thread requires the operating system to switch the CPU state to complete. This state transition requires processor time. If the content in the synchronization code block is too simple, the time consumed by the state transition It may take longer than the execution time of the user code."

This method is the way that synchronized originally realized synchronization, which is the reason for the low efficiency of synchronization before JDK 6. This type of lock that depends on the operating system Mutex Lock is called a "heavyweight lock". In order to reduce the performance cost of acquiring and releasing locks, JDK 6 introduced "biased locks" and "lightweight locks". ".

Therefore, there are currently three lock states, from low to high in order: bias lock, lightweight lock and heavyweight lock. The lock status can only be upgraded but not downgraded.


Bias lock


Biased lock means that a piece of synchronized code has been accessed by a thread, then the thread will automatically acquire the lock, reducing the cost of acquiring the lock. In most cases, the lock is always acquired by the same thread multiple times, there is no multi-thread competition, so there is a biased lock. The goal is to improve performance when only one thread executes synchronized code blocks. When a thread accesses the synchronized code block and acquires the lock, the thread ID of the lock bias is stored in the Mark Word. When the thread enters and exits the synchronized block, the CAS operation is no longer used to lock and unlock, but to check whether the Mark Word stores a bias lock pointing to the current thread. The introduction of biased locks is to minimize unnecessary lightweight lock execution paths without multi-threaded competition, because the acquisition and release of lightweight locks rely on multiple CAS atomic instructions, and biased locks only need to replace ThreadID Just rely on the CAS atomic instruction once. The biased lock will only release the lock when other threads try to compete for the biased lock, and the thread will not release the biased lock actively. To revoke a biased lock, you need to wait for a global security point (at this point where no bytecode is being executed), it will first suspend the thread that owns the biased lock to determine whether the lock object is locked. After revoking the bias lock, return to the state of lock-free (flag bit is "01") or lightweight lock (flag bit is "00"). Bias lock is enabled by default in JDK 6 and later JVM. You can turn off the bias lock through the JVM parameter:

-XX:-UseBiasedLocking=false, after closing the program will enter the lightweight lock state by default.


Lightweight lock


It means that when the lock is a biased lock and is accessed by another thread, the biased lock will be upgraded to a lightweight lock, and other threads will try to acquire the lock by spinning without blocking, thereby improving performance.

When the code enters the synchronization block, if the lock state of the synchronization object is unlocked (the lock flag is in the "01" state, whether it is a biased lock is "0"), the virtual machine will first create one in the stack frame of the current thread The space named Lock Record is used to store a copy of the current Mark Word of the lock object, and then copy the Mark Word in the object header to the lock record.

After the copy is successful, the virtual machine will use the CAS operation to try to update the Mark Word of the object to a pointer to the Lock Record, and point the owner pointer in the Lock Record to the Mark Word of the object. If this update action is successful, then this thread owns the object's lock, and the lock flag of the object Mark Word is set to "00", indicating that the object is in a lightweight lock state.

If the update operation of the lightweight lock fails, the virtual machine will first check whether the Mark Word of the object points to the stack frame of the current thread. If it is, it means that the current thread already owns the lock of this object, so you can directly enter the synchronization block to continue. Execute, otherwise it means that multiple threads compete for lock.

If there is currently only one waiting thread, the thread waits by spinning. But when the spin exceeds a certain number of times, or one thread is holding the lock, one is spinning, and there is a third visit, the lightweight lock is upgraded to a heavyweight lock.


Heavyweight lock


When upgrading to a heavyweight lock, the status value of the lock flag becomes "10". At this time, the pointer to the heavyweight lock is stored in the Mark Word. At this time, the threads waiting for the lock will enter the blocking state.



The partial lock solves the locking problem by comparing the Mark Word and avoids the execution of CAS operations. The lightweight lock solves the locking problem by using CAS operation and spin to avoid thread blocking and wake-up that affect performance. Heavyweight locks block all threads except the thread that owns the lock.


The above is about the  analysis of synchronized lock state changes~


Guess you like

Origin blog.51cto.com/15075523/2606415