Java Multithreading Basics-15: Optimization operations of synchronized in Java--lock upgrade, lock elimination, lock coarsening

From a summary of common lock strategies in concurrent programming , synchronized has the following characteristics:

  1. It starts with optimistic locking, and if lock conflicts occur frequently, it is converted to pessimistic locking.
  2. It starts with a lightweight lock implementation, and if the lock is held for a long time, it is converted to a heavyweight lock.
  3. When implementing lightweight locks, the spin lock strategy is most likely to be used.
  4. It is an unfair lock.
  5. It is a reentrant lock.
  6. Not a read-write lock.

This article introduces several optimization operations of synchronized, including lock upgrade, lock elimination and lock coarsening.

1. Lock upgrade

JVM divides synchronized locks into four states: no lock, biased lock, lightweight lock, and heavyweight lock. During the locking process, upgrades will be performed sequentially based on the actual situation. (**The current mainstream JVM implementation can only lock upgrades, but not lock downgrades!** It is not impossible to implement, but it may be because there are some costs that make the benefits and costs of doing so disproportionate, so it has not been implemented.)

The overall locking process (lock upgrade process): When locking is started, it is in the biased lock state; after encountering lock competition, it is upgraded to a spin lock (lightweight lock); when the competition becomes more intense, it will become a weight level lock (given to the kernel to block and wait).

1. Biased Locking

The first thread that attempts to lock enters the biased lock state first. Biased locking is an optimization technology used in the Java Virtual Machine (JVM) to improve thread synchronization performance. In a multi-threaded environment, synchronized operations on shared resources require the use of locks (synchronized) to ensure mutually exclusive access of threads. The traditional lock mechanism has the overhead of competition and context switching, which will have a certain impact on performance. Biased locks were introduced to reduce the cost of lock operations in the absence of competition.

Biased locking is not really "locking". It just allows the thread to mark the lock object first and record which thread a certain lock belongs to.

The basic idea is that when a thread acquires a lock and accesses a synchronized block of code, if there is no competition, then the next time the thread enters the synchronized block again, it does not need to acquire the lock again. This is because in the absence of competition, assuming a thread repeatedly accesses the synchronized code block, there is no need to compete for the lock every time. It only needs to determine whether the lock is in a biased state; if so, then directly enter the synchronized code block.

In layman's terms, if no other threads compete for the lock in the future, then there is no need to actually lock, thus avoiding the overhead of locking and unlocking. But once other threads try to compete for this lock, the biased lock is immediately upgraded to a real lock (lightweight lock), and other threads can only wait. This ensures both efficiency and thread safety.

How to determine whether there are other threads competing for the lock?

Note that bias locking is work done internally by synchronized. synchronized will lock an object . This so-called "biased lock" makes a mark in this object.

Since which thread the current lock belongs to has been recorded in the lock object at the beginning, it is easy to identify whether the thread currently applying for the lock is the thread recorded at the beginning.

If another thread is trying to lock the same object, it will also try to mark it first, only to find that it is already marked. So the JVM will notify the thread that comes first and let it upgrade the lock quickly.

Biased locking is essentially "delayed locking", that is, it is not locked if it can be unlocked, and unnecessary locking overhead is avoided as much as possible; however, the markings that should be made must still be made, otherwise it will be impossible to distinguish when actual locking is needed. .

Give an example to understand bias lock

Suppose the male protagonist is a lock and the female protagonist is a thread. If only the female protagonist and the male protagonist are ambiguous (that is, only this thread uses this lock), then even if the male protagonist and the female protagonist do not obtain a license to get married (avoiding high-cost operations), they can continue to live together.

But if a female partner appears at this time and tries to compete with the male protagonist and wants to have an affair with the male protagonist, then the female protagonist must make an immediate decision at this time. No matter how costly the operation of getting a marriage certificate is, it is bound to be completed (that is, the actual process is completed). Lock), making the female protagonist give up.

Therefore, biased lock = engaging in ambiguity ~~

2. Spin lock

**What is a spin lock? **Mentioned in the lock strategy article:

Spin lock is a typical lightweight lock implementation. It is usually purely user mode and does not need to go through the kernel mode. According to the previous method, after the thread fails to grab the lock, it enters the blocking state and gives up the CPU. It takes a long time before it can be scheduled again. But in fact, in most cases, although the current lock grab fails, the lock will be released soon, and there is no need to give up the CPU. At this time, you can use spin locks to deal with such problems.

Spin lock is a busy waiting lock mechanism. When a thread needs to acquire a spin lock, it will repeatedly check whether the lock is available instead of being blocked immediately. If the lock acquisition fails (the lock is already occupied by another thread), the current thread will immediately try to acquire the lock again, and continue to spin (idle) waiting for the lock to be released until the lock is acquired. The first attempt to acquire the lock fails, and the second attempt will come within a very short time. This ensures that once the lock is released by other threads, the current thread can obtain the lock as soon as possible.

Advantages: No CPU is given up, no thread blocking and scheduling are involved. Once the lock is released, the lock can be obtained as soon as possible.
Disadvantages: If the lock is held by other threads for a long time, CPU resources will continue to be consumed (busy waiting), but CPU resources are not consumed when suspended and waiting.

Spin locks are suitable for situations where the protection critical section is small and the lock occupies a short time, because spin consumes CPU resources. Spin locks are usually implemented using atomic operations or special hardware instructions.

As other threads enter lock competition, the biased lock state will be eliminated and the lightweight lock state will be entered, that is, an adaptive spin lock.

The lightweight lock here is implemented through CAS. Check and update a piece of memory through CAS (for example, comparing whether null is equal to the reference of the thread). If the update is successful, the lock is considered successful; if the update fails, the lock is considered occupied and the spin-like wait continues without giving up during the process. CPU resources.

(See detailed explanation of CAS algorithm )

The principle of CAS algorithm to implement spin lock

Since the spin operation keeps the CPU idling, which is a waste of CPU resources, the spin here will not continue forever, but will stop spinning after reaching a certain time or the number of retries. This is also called "adaptive".

3. Heavyweight lock

**What is a heavyweight lock? **Mentioned in the lock strategy article:

Simply put, lightweight locks are a locking strategy that makes the locking and unlocking process faster and more efficient, while heavyweight locks are a locking strategy that makes the locking and unlocking process slower and more efficient. The locking mechanism in heavyweight locks relies heavily on the mutex (mutex) provided by the OS.

  • A large number of kernel mode user mode switching.
  • It is easy to cause thread scheduling.

The cost of these two operations is relatively high, and once it involves switching between user mode and kernel mode, the efficiency is low.

If the competition becomes more intense, the spin cannot quickly acquire the lock state. It will expand into a heavyweight lock.

Although the spin lock can obtain the lock fastest, it consumes a lot of CPU (because the CPU idling quickly during spin). If the current lock competition is very fierce, for example, 50 threads compete for a lock, 1 thread competes for it, and the other 49 are waiting. With so many threads spinning and idling, the CPU consumption is very high. In this case, change the lock strategy and upgrade it to a heavyweight lock, allowing other threads to block and wait in the kernel (this means that the thread has to temporarily give up CPU resources and the kernel will perform subsequent scheduling).

(PS: Current mainstream operating systems such as Windows and Linux have high scheduling overhead. The system does not promise to complete the specified scheduling within xx time. In extreme cases, the scheduling overhead may be very large.

But there is another real-time operating system (such as vxworks), which can complete task scheduling at a lower cost, but sacrifices more other functions. It is used in special fields such as rocket launches where time accuracy is relatively high. )

If the competition becomes more intense, the spin cannot quickly acquire the lock state. It will expand into a heavyweight lock.

The heavyweight lock here refers to the mutex provided by the kernel.

  1. When a thread performs a locking operation, it first enters the kernel state.
  2. Determine whether the current lock is already occupied by another thread in kernel mode.
  3. If the lock is not occupied, the locking is successful and the user mode is switched back to the user mode.
  4. If the lock is occupied, the lock fails. At this time, the thread enters the lock waiting queue and hangs, waiting to be awakened by the operating system.
  5. After a series of "vicissitudes of life", the lock was finally released by other threads. At this time, the operating system also remembered the suspended thread, so it woke up the thread and let it try to reacquire the lock.

2. Lock elimination

Lock elimination is also a manifestation of "unnecessary, no locking". Different from lock upgrade, lock upgrade is an optimization method made by the JVM during the running phase of the program. Lock elimination is an optimization method during the program compilation stage. The compiler and JVM will detect whether the current code is executed in multiple threads or whether locking is necessary. If it is unnecessary but the lock is written, the lock will be automatically removed during the compilation process.

Some application code may use synchronized unnecessarily. For example, StringBuffer is thread-safe, and each of its key methods has the synchronized keyword added:

Part of the source code of StringBuffer

But there is a problem here: if StringBuffer is used in a single thread, thread safety issues are not involved. There is actually no need to lock at this time. Then the compiler will take action at this time and find that synchronized is not necessary. It will remove synchronized during the compilation stage, which is equivalent to the lock operation not being actually compiled.

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

At this point, every call to append involves locking and unlocking. But if this code is only executed in a single thread, then these locking and unlocking operations are unnecessary, and some resource overhead is wasted.

Lock elimination is generally a relatively conservative optimization method. After all, the compiler must ensure that the elimination operation is reliable. Therefore, lock elimination will only be implemented when it is absolutely certain, otherwise it will still be locked. At this time, other operating strategies will be used to optimize the lock (such as the above lock upgrade).

3. Lock roughening

The granularity of the lock refers to how much code is contained in the synchronized code block. The more code, the greater the granularity; the less code, the smaller the granularity.

Generally, when we write code, in most cases we want the lock granularity to be smaller. (Small lock granularity means less code is executed serially and more code is executed concurrently). If a scenario requires frequent locking and unlocking, the compiler may optimize this operation into a coarser-grained lock, that is, lock coarsening.

In the actual development process, fine-grained locks are used in the hope that other threads can use the lock when the lock is released. But in reality, there may not be other threads to seize this lock. In this case, the JVM will automatically coarsen the lock to avoid unnecessary overhead caused by frequent application and release of locks.

Give a chestnut to understand lock coarsening

Report work to the leader when you go to work. Your leader has assigned you three jobs: A, B, and C.
Reporting methods include:

  1. Make a phone call first, report the progress of work A, and hang up the phone; make another phone call, report the progress of work B, and hang up the phone; make another phone call, report the progress of work C, and hang up the phone. (You call the leader, and the leader answers your call, and the leader can't do anything else; if others want to call the leader, they can only block and wait. Each lock competition may introduce a certain waiting overhead. At this time, the overall efficiency It may be even lower.)
  2. Make a phone call, report work A, work B, and work C in one breath, and hang up the phone.

Obviously the second way is more efficient.

It can be seen that the synchronized strategy is relatively complex, and it is a very "intelligent" lock.

Guess you like

Origin blog.csdn.net/wyd_333/article/details/131817841