Pre-knowledge of locks

To fully understand the ins and outs of Java locking, you need to know the basics of locking: lock types, the cost of Java thread blocking, Markword.

The type of lock

Lock from macroscopic classification, divided into pessimistic lock and optimistic lock.

Optimistic locking

Optimistic locking is an optimistic idea that reads more than writes, and the likelihood of encountering concurrent writes is low. Every time to fetch the data that other people will not change, so will not lock, but at the time of update will assess the during that time, people have to update the data, at the time of writing in a first read the current version number, and then lock operation (compare with the previous version, if the same is updated), if failure were repeated to read more – write operation.

Optimistic locking in Java is basically implemented through the CAS operation, which is an atomic update operation that compares the current value with the incoming value. If the current value is the same, the update will fail.

Pessimistic locking

Pessimistic locking is pessimistic thinking, that is, the possibility of concurrent write is high because of the high number of writes. Every time I try to retrieve the data, I think someone else is going to change it, so every time I try to read or write the data, I lock it, and everyone else tries to read or write the data and blocks until I get the lock.

A pessimistic lock in Java is a lock in the Synchronize framework, and a lock in the AQS framework is a lock obtained by trying the CAS optimistic lock. If the lock fails to be obtained, the lock is converted to a pessimistic lock, such as ReentrantLock.

The cost of thread blocking

Java threads are mapped to the operating system native threads, and blocking or waking a thread requires the operating system to step in and switch between the user state and the core state, which consumes a lot of system resources. Because of user mode and kernel mode have their own special memory space, dedicated registers, such as user mode to kernel mode switch need to pass to a number of variables and parameters to the kernel, the kernel will also need to protect the user mode in some register values, variables, etc., when switching to kernel mode called after the user mode switch back to continue to work.

  1. If thread-state switching is a high-frequency operation, it will consume a lot of CPU processing time;
  2. For simple blocks of code that need to be synchronized, the acquisition of the lock suspend takes longer than the user code executes, then this synchronization strategy is obviously bad.

Synchronized causes threads that do not compete for the lock to block, so it is a heavyweight synchronization operation in the Java language, known as a heavyweight lock. To mitigate these performance issues [synchronized], the JVM, starting with 1.5, introduced light-weight locks and bias locks, with spin-locks enabled by default, both of which are optimistic locks.

Understanding the costs of Java thread switching is one of the foundations of understanding the advantages and disadvantages of various locks in Java.

Mark Word

Markword is a part of the data structure of Java objects. To understand the structure of Java objects in detail, you can click here. Here is only a detailed introduction of Markword, because the markword of an object is closely related to various types of Java locks.

The length of markWord data is 32bit and 64bit respectively in 32-bit and 64-bit VMS (without compression pointer). The last 2 bits are the lock status flag bits, which are used to mark the status of the current object. The status of the object determines the content of markWord storage, as shown in the following table:

state Sign a Store content
unlocked 01 Object hash code, object generational age
Lightweight locking 00 A pointer to a lock record
Inflation (heavyweight lock) 10 Pointer to perform heavyweight locking
The GC tag 11 Empty (no need to record information)
Can be biased 01 Biased thread ID, biased timestamp, object generational age

The markWord structure of a 32-bit virtual machine in different states is shown in the following figure:

Understanding markword structure is helpful to understand the process of locking and unlocking Java locks.

The four types of Java locks mentioned earlier are heavyweight, spin, lightweight, and bias locks. Different locks have different characteristics, and each lock performs well only in its particular scenario. No lock in Java is efficient in all cases. The reason why so many locks are introduced is to deal with different situations;

In Java lock

Biased locking

Java Biased Locking is a multithreaded optimization introduced in Java6 that further improves program performance by eliminating synchronization primitives in resource-free situations.

Biased locking, as the name implies, is biased in favor of the first thread to access the lock. If a synchronization lock is run by only one thread and there is no contention between multiple threads, then the thread does not need to trigger synchronization. In this case, a biased lock is applied to the thread. If, during runtime, another thread preempts the lock, the thread holding the biased lock is suspended, and the JVM removes the biased lock from it, returning the lock to a standard lightweight lock.

Biased lock acquisition process

  1. Access whether the biased lock flag in Mark Word is set to 1, whether the lock flag bit is 01, and confirm that the state is biased.
  2. If the state is biased, test whether the thread ID points to the current thread. If yes, go to Step 5; otherwise, go to Step 3.
  3. If the thread ID does not point to the current thread, the lock is contested through the CAS operation. If the race is successful, set the thread ID in Mark Word to the current thread ID, and then go to 5; If the race fails, go to 4.
  4. If the CAS fails to obtain a biased lock, there is a contention. When safepoint is reached, the thread that acquired the biased lock is suspended, the biased lock is upgraded to a lightweight lock, and the thread that was blocked at the safepoint continues to execute the synchronization code. (Undo biased lock causes stop the world)
  5. Execute the synchronization code.

Note: Reaching safePoint in step 4 causes Stop the World for a short time.

Biased lock acquisition process

The cancellation of biased locks is mentioned in step 4 above. A biased lock is only released by a thread holding a biased lock if another thread tries to compete for it. A thread does not actively release a biased lock. When a bias lock is undone, it waits for the global safe point (at which point no bytecode is executing), it first suspends the thread that owns the bias lock, determines whether the lock object is locked, and then returns to the unlocked (flag bit “01”) or lightweight (flag bit “00”) state.

Application scenarios of biased locking

While performing synchronized block, always only one thread in it has not performed before releasing the lock, no other threads to execute the synchronized block, the lock without competition, once the competition will upgrade for lightweight locks, upgrade for lightweight locked when you need to reverse bias, revocation of biased locking time will lead to stop the world; In the case of a lock race, bias locking does a lot of extra work, especially when reversing bias locking will lead to a safe point, which will cause STW and performance degradation, and should be disabled in this case.

View pause – Safety point pause log

Pause to see the security point, can open the security log, by setting the JVM parameter – XX: + PrintGCApplicationStoppedTime will hit the system stop time, Add – XX: + PrintSafepointStatistics – XX: PrintSafepointStatisticsCount = 1 these two parameters will print out the detailed information, you can look at the use of biased locking to pause, time is very short, but the contention for serious cases, There will be a lot of pauses;

Note: The security point log is not always open, only open during troubleshooting:

  1. Safe point logs are output to stdout by default, because of cleanliness of the stdout log, and because files redirected by stdout may be locked if they are not in /dev/shm.
  2. For very short pauses, such as unbiased locking, printing costs more than the pauses themselves.
  3. The security point log is printed within the security point, which increases the pause time of the security point.

If to open on the production system, and then add the following four parameters: – XX: XX: + UnlockDiagnosticVMOptions – -displayvmoutput -xx :+LogVMOutput -xx :LogFile=/dev/ SHM /vm.log Open the Diagnostic command (only more flag options are available, no one flag is actively activated), and close the output of VM logs to stdout. Output to a separate file,/dev/shm directory (memory file system).

The first part is the timestamp, the type of VM Operation;

The second part is a thread overview, surrounded by brackets [total: total number of threads in the safe point, initially_RUNNING: number of threads that were running at the start of the safe point, wait_TO_block: number of threads that need to wait for the VM Operation to pause before it starts].

(spin: the time it takes to wait for a thread to respond to the safepoint call, block: the time it takes to pause all threads, sync: equal to spin+block, which is the time it takes from the start to the safepoint, This can be used to determine how long it takes to get to the safe point, cleanup: time to cleanup, vmop: time to actually execute VMOperation. The most important is vmop.

As you can see, those many, but very short, security points are all RevokeBias, which disables bias locking for highly concurrent applications.

The Jvm turns bias locking on/off

  • Open bias lock:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
  • To close deviation lock:-XX:-UseBiasedLocking

Lightweight lock

Lightweight locking is upgraded from bias locking, which runs when one thread enters a synchronized block and is upgraded to lightweight when a second thread enters a lock contention.

Lightweight locking process

  1. When the code enters the synchronization block, if the synchronization object Lock state is unlocked (the Lock flag bit is “01” and the bias Lock is “0”), the VIRTUAL machine will first create a space named Lock Record in the stack frame of the current thread, which is used to store the copy of the Lock object’s current Mark Word. The official is called Swat Mark Word. The state of the thread stack and object header is as follows:

  2. Copy Mark Word in the object header to the lock record;

  3. After the copy is successful, the VM will use CAS operation to update the Mark Word of the object to the pointer pointing to the Lock Record, and the owner pointer in the Lock Record points to the Object Mark Word. If the update is successful, go to Step 4. Otherwise, go to Step 5.

  4. If the update is successful, the thread has the lock on the object, and the lock bit on the object Mark Word is set to “00”, which means that the object is in a lightweight locked state. The thread stack and the object head are in the following state:

  5. If the update fails, the vm first checks to see if the object’s Mark Word points to the stack frame of the current thread. If it does, the thread already has the lock on the object and can proceed directly to the synchronization block. Otherwise, multiple threads are competing for the lock, the lightweight lock is swollen to a heavyweight lock, the status of the lock flag is changed to “10”, the Mark Word is stored as a pointer to the heavyweight lock (mutex), and subsequent threads waiting for the lock are blocked. The current thread tries to acquire the lock using spin, which is the process of looping to acquire the lock so that the thread does not block.

Lightweight lock release process

Release lock thread view: The switch from a lightweight lock to a heavy-weight lock occurs during the release of the lock by a lightweight lock. When it acquired the lock, it copied the markword of the lock object header. When it released the lock, if it found that another thread had attempted to acquire the lock during the time it held the lock, and the markword had been modified by that thread, and there was a discrepancy between the two, Switch to weight lock.

All display Mark Word is different from the original MarkWord because the heavyweight lock has been changed. How to fix it? Compare obj’s markword status with obj’s markword status before entering mutex. Compare obj’s markword status with obj’s markword status before entering mutex, compare obj’s Markword status with OBj’s markword status. At this point, if the thread has already released the Markword, then it can enter the thread directly after passing the CAS, without entering the Mutex.

Try to acquire a lock Thread view: If a thread tries to acquire a lock while the lightweight lock is being held by another thread, it modifies markword to modify the heavyweight lock to indicate that it is time to enter the weight lock.

One more note: the thread waiting for the light lock does not block, it keeps spinning and waiting for the lock, and modifying the Markword as mentioned above.

This is spin-locking, where a thread attempting to acquire a lock is not suspended when it does not acquire the lock, but instead performs an empty loop called spin. After several spins, the lock is suspended if it has not been acquired, and the code executes when the lock is acquired.

spinlocks

Spinlocks principle is very simple, if the thread holding the lock can lock is released in a very short time resources, and the thread lock wait for competition there is no need to do between kernel mode and user mode switch into the block pending state, they just need to wait for a while (spin), such as thread holding the lock immediately after releasing the lock locks, thus avoiding the consumption of user and kernel thread switching.

However, thread spin costs CPU, which means that the CPU is doing idle work. If the lock is not acquired, the thread can not use the CPU spin to do idle work all the time, so you need to set a maximum time for the spin wait. If the thread holding the lock executes for longer than the maximum spin wait time and does not release the lock, other threads that are contending for the lock will not acquire the lock within the maximum wait time, and the contending thread will stop spinning and block.

Advantages and disadvantages of spin-locks

Advantages: Spin-locking minimizes thread blocking as much as possible. This gives a significant performance boost for code blocks that are less competitive for locks and hold locks for very short periods of time, since the cost of spin-locking is less than the cost of a thread blocking, suspending and reactivating operation, which causes the thread to make two context switches!

Missing: But if the lock of the competition is intense, or thread holding the lock need long time to occupy the lock synchronization block, this time is not suitable for using a spin lock, because of the spin lock before acquiring a lock is CPU doing this all the time, of the pot does not move bowels, competition at the same time a large number of threads in a lock, and can lead to acquiring a lock for a long time, The cost of spinning a thread is greater than the cost of blocking and suspending a thread, and the CPU is not available to other threads that need it, resulting in a waste of CPU. So in this case we’re going to turn off the spin lock;

Spin-lock time threshold

The purpose of a spin lock is to hold CPU resources until the lock is acquired. But how to choose the execution time of the spin? If the spin execution time is too long, a large number of threads will be spinning and take up CPU resources, which will affect the overall system performance. Therefore, the period of spin is selected as extra important!

In 1.6, adaptive spin locking was introduced. Adaptive spin locking 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. A thread context switch time is considered to be the best time, and the JVM has also optimized for the current CPU load:

  1. If the load average is smaller than CPUs, spin all the time;
  2. If more than (CPUs/2) threads are spinning, then the thread blocks directly afterwards;
  3. Delay spin time (spin count) or block if the spinning thread finds that the Owner has changed;
  4. If the CPU is in power saving mode, stop spinning;
  5. The worst case of spin time is the memory delay of the CPU (the time between CPU A storing A piece of data and CPU B knowing it directly);
  6. The differences between thread priorities are appropriately abandoned when spinning;

The opening of a spin lock

In JDK1.6, -xx :+UseSpinning opens; -xx :PreBlockSpin=10; After JDK1.7, this parameter is removed and controlled by the JVM.

Heavyweight Synchronized

The synchronized keyword is used before JDK1.5 to ensure that only one method or block of code can be accessed at a time at runtimeA critical regionIt also guarantees memory visibility for shared variables.

  • In common synchronization methods, the lock is the current instance object;
  • Statically synchronized methods, where the lock is a class object of the current class;
  • Synchronized method blocks, where the lock is an object in parentheses;

The realization of Synchronized

It has multiple queues, and when multiple threads access an object monitor together, the object monitor stores those threads in different containers.

  1. Contention List: Contention queue, where all threads requesting a lock are placed on the Contention queue first.
  2. Contention for candidate resources in the Entry List is moved to the Entry List.
  3. Wait Set: Blocks the queue where threads that are blocked by calling Wait are placed;
  4. OnDeck: At any given time, at most one thread is competing to lock resources, and that thread is called OnDeck;
  5. Owner: The thread that has obtained the resource is called Owner.
  6. ! Owner: The thread that currently releases the lock.

The JVM takes data from the end of the queue one at a time to lock the ContentionList (OnDeck), but in the case of concurrency, the ContentionList is accessed by a large number of concurrent threads for CAS access. To reduce the ContentionList race, the JVM moves some threads to the EntryList as candidate contention threads. The Owner thread migrates some of the threads in the ContentionList to the EntryList when unlocking, and specifies one of the threads in the EntryList as the OnDeck thread (usually the first thread to enter). The Owner thread does not pass the lock directly to the OnDeck thread, but instead gives OnDeck the right to compete the lock, and OnDeck needs to re-compete the lock. This sacrifice of fairness, but greatly improves the throughput of the system, is also known in the JVM as “competitive switching.”

When the OnDeck thread gets the lock resource, it becomes the Owner thread, while those that do not get the lock resource remain in the EntryList. If the Owner thread is blocked by the WAIT method, it is moved to the WaitSet queue until it is awakened by a notify or notifyAll at some point and then re-entered into the EntryList.

Threads in ContentionList, EntryList, WaitSet are all blocked by the operating system (using the Pthread_mutex_lock kernel function in Linux).

Synchronized is not a fair lock. When a thread enters the ContentionList, the waiting thread tries to spin the lock first. If it fails to acquire the lock, it enters the ContentionList, which is obviously unfair to threads already queued. Another unfair thing is that the thread that spin acquired the lock may also directly preempt the lock resource of the OnDeck thread.

The evolution of Synchronized locks

  1. Check whether Mark Word contains the ID of the current thread. If yes, the current thread is in biased lock.
  2. If not, use CAS to replace Mard Word with the ID of the current thread. If successful, it means that the current thread obtains bias lock, and set bias flag bit 1.
  3. If it fails, the competition occurs, the biased lock is revoked, and the lock is upgraded to lightweight.
  4. The current thread uses CAS to replace the Mark Word in the object header with the lock record pointer. If successful, the current thread acquires the lock.
  5. If this fails, another thread is competing for the lock, and the current thread tries to spin to acquire the lock.
  6. If the spin is successful, it is still lightweight;
  7. If the spin fails, it is upgraded to a heavyweight lock;

In the case of all the locks is enabled to thread into the critical region will first to get biased locking, if there have been a biased locking, will try to get a lightweight lock, enable the spin lock, if the spin also does not have access to the lock, use the heavyweight lock, without access to lock the thread blocking hangs, until the thread holding the lock execution of the synchronized block wake them;

Biased locking is used in the case of no lock contention, namely synchronous open until the current thread is performed, no other thread will execute the synchronized block, once you have a second thread contention, biased locking will be upgraded to a lightweight lock, if lightweight spin lock arrived at the threshold, no access to the lock, lock will be upgraded to a heavyweight;

Note: Biased locking should be disabled if thread contention is intense.

Pay attention to the public number data craftsman, focus on Java big data field offline, real-time technology dry goods regular sharing! Personal Website: www.lllpan.top