Upgrade of synchronized locks in java (biased locks, lightweight locks and heavyweight locks)

Java synchronization lock pre-knowledge points

  1. If you use locks in coding, you can use the synchronized keyword to lock methods and code blocks synchronously
  2. Synchronized synchronization lock is a built-in implicit lock of jvm (relative to Lock, implicit lock and release)
  3. The realization of Synchronized synchronization lock depends on the operating system. Acquiring and releasing the lock for system calls will cause the user mode and the kernel mode to switch.
  4. Before jdk1.5, lock can only be used synchronized, 1.6 introduces Lock synchronization lock (based on java implementation, explicit lock and release, better performance)
  5. jdk1.6 puts forward the concept of biased lock, lightweight lock, and heavyweight lock for Synchronzied synchronization lock (in fact, it optimizes the performance of synchronized and minimizes the context switching caused by lock competition)
  6. Whether using synchronized or Lock, thread context switching is unavoidable
  7. One of the performance optimizations of Lock relative to synchronized is: when the thread is blocked, Lock acquiring the lock will not cause the switch between user mode and kernel mode, while synchronized will (see point 3). But thread blocking will lead to context switching (see point 6)
  8. The blocking and awakening of java threads depend on operating system calls, resulting in switching between user mode and kernel mode
  9. The switch between user mode and kernel mode mentioned earlier is a process context switch rather than a thread context switch.

This article focuses on the upgrade of synchronized locks.

synchronized synchronization lock

java object header

Every java object has an object header, which is composed of a type pointer and a tag field. In a 64-bit virtual machine, the compressed pointer is not turned on, the tag field occupies 64 bits, and the type pointer occupies 64 bits, totaling 16 bytes. The lock type information is the last 2 digits of the tag field: 00 means lightweight lock, 01 means no lock or biased lock, 10 means heavyweight lock; if the bottom 3 digit is 1, it means that this type of biased lock is activated, which is 0 Indicates that the bias lock of the class is disabled. As shown in the picture below, the picture comes from wiki: https://wiki.openjdk.java.net/display/HotSpot/Synchronization

The left column indicates the bias lock is enabled (box 1), and the right column indicates the bias lock is disabled (box 3). 1 and 3 both represent the initial state of no lock. If the bias lock is enabled, the lock upgrade step should be 1->2->4->5, if the bias lock is disabled, the lock upgrade step is 3->4->5.

I used jdk8. I printed the parameters and looked at it. By default, the bias lock is enabled. If it is disabled: -XX:-UseBiasedLocking

There are several other parameters about the bias lock:

Pay attention to the BiasedLockingStartupDelay parameter, the default value is 4000ms, which means that the virtual machine starts with a delay of 4s before using the biased lock (use the lightweight lock first).

Bias lock

The scenario of biased lock processing is that most of the time, only the same thread is requesting a lock, and there is no situation where multiple threads compete for locks. Look at the red box 2 of the object header, there is a thread ID field: when the thread is locked for the first time, jvm sets the current thread address to the thread ID flag through cas, and the last 3 bits are 101. The next time the same thread acquires the lock, only check whether the last 3 digits are 101, whether it is the current thread, whether the epoch is equal to the epoch of the lock object class (the wiki says that there is no cas setting again for the current multi-processor Optimization of cas operation).

The performance improvement brought by the biased lock optimization refers to the avoidance of the user mode and kernel mode switching caused by the system call of the lock, because the same thread obtains the lock, there is no need to make a system call every time the lock is acquired .

If the thread ID does not match the current thread when the current thread acquires the lock (in the unlocked state), the biased lock will be revoked and biased again to the current thread. If the number of times reaches the value of BiasedLockingBulkRebiasThreshold, the default is 20 times, and the biased lock of the current class becomes invalid. The impact is that the epoch value changes, the epoch value of the locked class is increased by 1, and the subsequent lock object will re-copy the epoch value of the class to the epoch mark bit in the figure. If the total number of revocations reaches the value of BiasedLockingBulkRevokeThreshold (40 times by default), the bias lock of the current class is disabled, which is the right column of the object header, and the lock starts directly from the lightweight lock (the lock is upgraded).

Revocation of biased locks is a very troublesome process. It requires all threads to reach a safe point (STW occurs), traverse the thread stacks of all threads to check whether the lock object is held, to avoid lock loss, and to deal with epochs.

If there is multi-threaded competition, the bias lock has also begun to escalate.

Lightweight lock

The scenario of lightweight lock processing is that different threads request locks at the same time period (threads are executed alternately). Even if there are multiple threads competing for locks at the same time period, the thread that acquired the lock holds the lock for a very short time, and the lock is released soon.

When the thread is locked, it is judged that it is not a heavyweight lock, and a space will be opened in the current thread stack. As Li Xiaoran, copy the tag field of the lock object header (copying is to make a record, because the lock object header will be copied later. The value of the tag field is replaced with the space address of the tag field just copied, just like the pointer to lock record part of the picture in the object header. As for the last 2 bits, it is 00 because of memory alignment). Then based on the CAS operation, the address of the copied tag field is set to the value of the tag bit of the lock object header. If it succeeds, the lock is acquired. If it is judged that it is not a heavyweight lock when the lock is locked, and the last two digits are not 01 (coming from a biased lock or a lock-free state), it means that the thread has already held it. If the current thread is in (reentrant required), Then set a 0, here is a stack structure, just push a 0 directly. When the lock is finally released, the stack is popped, and the last element records the value of the original tag field of the lock object, which is then set to the lock object header through CAS.

Note that when acquiring the lock, cas fails, the current thread will spin for a while, reaching a certain number of times, upgrade to the heavyweight lock, the current thread will also be blocked.

Heavyweight lock

The heavyweight is the synchronization lock we usually say, which is the implementation of java-based locks. System calls are made when acquiring and releasing locks, which leads to context switching.

About spin lock

Regarding the spin lock, I have two main explanations: 1. It is a lightweight lock that fails to compete, and it will not immediately expand to a heavyweight but will spin a certain number of times to try to acquire the lock; 2. It is a heavyweight lock. If the competition fails, it will not be blocked immediately, and it will spin a certain number of times (a self-tuning algorithm is involved here). Regarding this description, you still need to see the source code of jvm to determine which is true: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/synchronizer. cpp

Parameters of the print bias lock

as follows:

-XX:+UnlockDiagnosticVMOptions
-XX:+PrintBiasedLockingStatistics

I acquire the same lock in the main method loop, and the print result is as follows:

    public static void main(String[] args) {
        int num = 0;
        for (int i = 0; i < 1_000_000000; i++) {
            synchronized (lock) {
                num++;
            }
        }
    }

Guess you like

Origin blog.csdn.net/x763795151/article/details/115059756