In-depth understanding of jvm-synchronized (thread-safe implementation)

This article is a reading note

1. Implementation method-Mutually exclusive synchronization (mutually exclusive synchronization is a pessimistic concurrency strategy)

Synchronization means that when multiple threads access the shared data concurrently, the shared data is guaranteed to be used by only one thread at the same time.
Mutual exclusion is a means to achieve synchronization. Critical section (CriticalSection), mutex (Mutex), and semaphore (Semaphore) are common methods of mutual exclusion.
Mutual exclusion is the cause and synchronization is the result; mutual exclusion is the method and synchronization is the end.


In Java, the most basic means of mutually exclusive synchronization is the synchronized keyword:

After the synchronized keyword is compiled by Javac, two bytecode instructions, monitorenter and monitorexit, are formed before and after the synchronization block.

When executing the monitorenter command, first try to acquire the lock of the object. If the object is not locked, or the current thread already holds the lock for that object, the value of the lock counter is increased by one, and the value of the lock counter is decreased by one when the monitorexit instruction is executed. Once the counter value is zero, the lock is released. If acquiring the object lock fails, the current thread should be blocked and wait until the object requesting the lock is released by the thread holding it.

Holding a lock is a heavy-weight operation. If you want to block or wake up a thread , you need the operating system to help complete it. This inevitably falls into the transition from user state to core state. Conversion requires a lot of processor time.

Secondly, ReentrantLock under the JUC package can also be achieved (compared to synchronized with the following features):

· Waitable interruption: when the thread holding the lock does not release the lock for a long time, the waiting thread can choose to give up waiting and handle other things instead.

· Fair lock: when multiple threads are waiting for the same lock, they must acquire the locks in sequence according to the chronological order of applying for locks; unfair locks do not guarantee this. When the lock is released, any thread waiting for the lock There is a chance to obtain a lock. The lock in synchronized is not fair, ReentrantLock is also unfair by default, but you can use fair locks through the constructor with Boolean values. However, once the fair lock is used, it will cause the performance of ReentrantLock to drop sharply, which will obviously affect the throughput.

· Lock binding multiple conditions: refers to a ReentrantLock object can bind multiple Condition objects at the same time. In synchronized, the lock object's wait () and its notify () or notifyAll () method can achieve an implicit condition. If you want to associate with more than one condition, you have to add an additional lock; ReentrantLock does not need to do this, just call the newCondition () method multiple times.

Lock optimization

Spin lock and adaptive spin

That is, spin (busy) waiting to acquire the lock, which avoids the performance loss of context switching after entering the blocking state when waiting for the lock to be acquired, and then obtaining the lock.

You can use the -XX: + UseSpinning parameter to turn on, JDK 6 has been changed to the default

We can let the thread that requested the lock "wait a little while", but do not give up the execution time of the processor to see if the thread holding the lock will soon release the lock. In order to make the thread wait, we only need to let the thread perform a busy loop (spin), this technique is called spin lock.

If the lock is occupied for a short time, the effect of spin waiting will be very good, otherwise if the lock is occupied for a long time, the spin thread will only consume processor resources in vain

Adaptive means that the spin time 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, the spin wait 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, allowing the spin wait to continue Relatively longer, such as lasting 100 busy cycles. On the other hand, if a spin rarely succeeds in acquiring a lock for a certain lock, it may be possible to directly omit the spin process when acquiring this lock in the future to avoid wasting processor resources.

Lock elimination

Lock elimination means that the virtual machine instant compiler requires synchronization for some code while it is running, but it eliminates locks that are detected as having no shared data contention.

The main decision basis for lock removal is data support from escape analysis

The basic behavior of escape analysis is to analyze the dynamic scope of an object: when an object is defined in a method, it may be referenced by an external method, for example, passed as a call parameter to other places, called method escape.

Roughening

If the virtual machine detects such a series of fragmented operations that lock the same object, it will extend (roughen) the synchronization range of the lock to the outside of the entire operation sequence;

E.g:

lock a(){...}

b(){
a();
a();
a();
}
//那么只会再b()上加一次锁

Lightweight lock (for no competition)

Objective:
Under the premise of no multi-thread competition, reduce the performance consumption of traditional heavyweight locks using the operating system mutex.

Prerequisite knowledge: The
Object Header of the HotSpot virtual machine is divided into two parts. The first part is used to store the runtime data of the object itself, such as HashCode, GC Generation Age (Generational GC Age), etc. The length of this part of the data will occupy 32 or 64 bits in the 32-bit and 64-bit Java virtual machines, respectively, which is officially called "Mark Word".
Insert picture description here
This part is the key to realize lightweight lock and bias lock.

Lightweight lock work introduction:
when the code is about to enter the synchronization block, if this synchronization object is not locked (the lock flag is "01" state), the virtual machine will first establish a lock named in the current thread's stack frame The record ** (Lock Record) space is used to store the copy of the current MarkWord of the lock object (the official adds a Displaced prefix to this copy, which is Displaced Mark Word **)

After that, the virtual machine will use the CAS operation to try to update the Mark Word of the object to a pointer to Lock Record . If this update action is successful, it means that the thread owns the lock of this object , and the lock mark bit (the last two bits of Mark Word) of the object Mark Word will change to "00" , indicating that this object is in a lightweight lock status.

If this update operation fails , it means that there is at least one thread competing with the current thread to acquire the object's lock . 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 the object, then it can be directly entered into the synchronization block to continue execution, otherwise it means that the lock object has been Other threads preempted . If more than two threads compete for the same lock, the lightweight lock is no longer valid , and it must be expanded to a heavyweight lock. The status value of the lock flag becomes "10", which is stored in Mark Word. Is the pointer to the heavyweight lock (mutexe), and the thread waiting for the lock must also enter the blocking state.

The unlocking process is also carried out through CAS operation. If the Mark Word of the object still points to the lock record of the thread, then use the CAS operation to replace the current Mark Word of the object and the Displaced Mark Word copied in the thread. If it can be successfully replaced, the entire synchronization process is successfully completed;

Lightweight locks can improve program synchronization performance based on the rule of thumb that "for most locks, there is no competition in the entire synchronization cycle."

Therefore, in the case of competition, lightweight locks will be slower than traditional heavyweight locks.

The following figure is a flowchart of the collision caused by lock competition:
Insert picture description here

Bias lock

The purpose is to eliminate the synchronization primitives of the data without competition, and further improve the performance of the program.
Lightweight lock is to use the CAS operation to eliminate the mutex used by synchronization without competition. The biased lock is to eliminate the entire synchronization without competition. Even the CAS operation is not done.

It means that the lock will be biased towards the first thread to acquire it. If the lock has not been acquired by other threads during the subsequent execution, the thread holding the biased lock will never need to synchronize again. .
No synchronization required: no need to update the MarkWord of the object with CAS

When the lock object is acquired by the thread for the first time, the virtual machine will set the flag bit in the object header to "01" and the bias mode to "1", indicating that it enters bias mode . At the same time, the CAS operation is used to record the ID of the thread that obtained the lock in the Mark Word of the object. If the CAS operation is successful, each time the thread holding the biased lock enters the lock-related synchronization block, the virtual machine can no longer perform any synchronization operations (such as locking, unlocking, and updating of Mark Word, etc.).
Insert picture description here
Sometimes using the parameter -XX: -UseBiasedLocking to prohibit biased lock optimization can improve performance.

Comparison of the advantages and disadvantages of the above locks

Insert picture description here

2. Implementation method-non-blocking synchronization

The optimistic concurrency strategy based on conflict detection, in layman's terms, is to operate first regardless of risk. If there is no other thread competing for shared data, the operation is directly successful; if the shared data is indeed contended and conflicts occur, then To carry out other compensation measures, the most commonly used compensation measure is to retry continuously until there is no competition for shared data. Also known as lock-free

The atomic instructions provided by hardware that implements non-blocking synchronization include:

  • Test and set (Test-and-Set);
  • Get and increase (Fetch-and-Increment);
  • Swap
  • Compare-and-swap (hereinafter referred to as CAS);
  • Load link / condition store (Load-Linked / Store-Conditional, hereinafter referred to as LL / SC).

3. Implementation method-no synchronization scheme

Reentrant Code (Reentrant Code): means that you can interrupt the code at any time during the execution of the code and turn to execute another piece of code (including recursive call to itself), and after the control returns, the original program will not appear any Errors will not affect the results.

Thread local storage (Thread Local Storage): If the data required in a piece of code must be shared with other code, then see if the code that shares the data can be guaranteed to execute in the same thread. If it can be guaranteed, we can limit the visible range of the shared data to the same thread, so that without synchronization, it can also ensure that there is no data contention between threads.

(ThreadLocal implements thread safety), abbreviated as space for time
, provides a copy of the variable for each thread, so as to achieve simultaneous access without interference

4. Lock memory semantics

The release of the lock-acquire the established happens-before relationship

Insert picture description here
1) According to the rules of program sequence, 1 happens-before 2, 2 happens-before 3; 4 happens-before 5, 5 happens-before 6.

2) According to the monitor lock rule, 3 happens-before 4.

3) According to the transitivity of happens-before, 2 happens-before 5.

Memory semantics of lock release and acquisition

❑ Thread A releases a lock. In essence, thread A sends a message (modified by thread A to a shared variable) to a thread that will acquire the lock next.

❑ Thread B acquires a lock. In essence, thread B received a message from a previous thread (modification of shared variables before releasing the lock).

❑ Thread A releases the lock, and then thread B acquires the lock. This process is essentially that thread A sends a message to thread B through the main memory.

ReentranLock example

Published 37 original articles · won praise 6 · views 4636

Guess you like

Origin blog.csdn.net/littlewhitevg/article/details/105579769