[JUC Advanced] 05. Bias lock

Table of contents

1 Introduction

2. Bias lock

2.1. Basic principles

2.2. Usage scenarios

3. Obtain a biased lock

4. When to revoke

4.1. Arrive at the safe point

4.2. Other threads try to compete for biased locks

4.3. Recalculate hashcode

5. Summary


1 Introduction

Biased lock is an important lock mechanism in Java concurrent programming. It is optimized for specific threads, thereby improving concurrency performance. This locking mechanism is very common in multi-threaded scenarios, and is especially suitable for some application scenarios where reads and writes are separated.

2. Bias lock

2.1. Basic principles

Biased lock is a thread-friendly locking mechanism. Its core idea is to determine whether to enable biased locks by identifying and tracking threads and dynamically analyzing lock competition conditions.

When a thread accesses an object marked as a synchronization block, if the object is not occupied by other threads, the thread will directly acquire the lock of the object; if the object is already occupied by other threads, the thread will enter spin state, constantly checking whether the object is occupied by other threads until the lock of the object is acquired.

Simply put: if a thread acquires a lock, the lock enters biased mode. When this thread requests the lock again, there is no need to do any synchronization operations. This saves a lot of operations on lock applications and improves performance. This is an effort made by JDK for lock optimization.

Therefore, for occasions where there is almost no lock competition, biased locks have a better optimization effect, because it is very likely that the same thread requests the lock multiple times in a row. For occasions where lock competition is fierce, it is not ideal, because in this case, the most likely locks are from different threads.

2.2. Usage scenarios

Biased locks are mainly used to solve performance problems caused by read-write locks. In the read-write lock, each read and write operation needs to be locked and unlocked, and these operations consume a lot of CPU time.

Biased locks use an optimistic locking mechanism to divide threads into two types: read threads and write threads. For read threads, as long as no write thread is modifying the shared data, the read thread can directly access the shared data without Perform lock and unlock operations. When a writing thread is modifying shared data, other threads need to spin and wait, which improves the concurrent performance of the program to a certain extent.

3. Obtain a biased lock

Starting from JDK6, the HotSpot virtual machine has enabled the -XX:UseBiasedLocking parameter, and the biased lock is enabled by default. When the lock object is acquired online for the first time, the virtual machine will set the flag bit in the object header to "01" and set the bias mode to "1", which means entering the bias mode. At the same time, use the CAS operation to record the ID of the thread that acquired the lock in the Mark Word of the object. If the CAS operation is successful, every time the thread holding the biased lock enters the synchronization block related to the lock, the virtual machine can no longer perform any synchronization operations (such as locking, unlocking, and updating operations on Mark Word, etc.).

Once another thread tries to acquire the lock, the bias mode will immediately end. According to whether the lock object is currently locked or not, it is decided whether to cancel the bias (the bias mode is set to "0"), and the flag bit is restored to unlocked (flag bit is "01") or light-weight locked (flag bit is "0") after revocation. 00") state.

Sort out the flow chart:

We can see that when the object enters the biased state, most of the Mark Word space is used to store the thread ID holding the lock, and the original space is the stored object hash code.

In fact, there is no space to store the hash value in the object header of the biased lock.

The implementation of biased locks in jvm is as follows:

// 偏向锁加锁操作  
bool BiasedLocking::biased_locking_acquire(oop obj, JavaThread* thread) {  
    int thread_id = get_thread_id();  
    int* lock_word = get_header_address(obj);  
  
    // 检查是否已经存在锁拥有者  
    if (*lock_word & 0x1) {  
        return false; // 已经存在锁拥有者,直接返回false  
    }  
  
    // 检查是否需要升级为重量锁  
    if ((*(lock_word) & 0x2) == 0) {  
        if (Atomic::cmpxchg(EXPECTED_BIAS, lock_word, lock_word - 1) != (int)thread_id - 1) {  
            return false; // 升级失败,锁已经被其他线程获取,直接返回false  
        } else {  
            // 升级成功,将线程ID写入锁拥有者字段中,并标记为重量锁  
            *lock_word = thread_id | 0x2;  
            thread->set_next(NULL); // 断开与队列中其他线程的连接  
            return true; // 升级成功,返回true  
        }  
    } else {  
        return true; // 已经是重量锁,直接返回true  
    }  
}  
  
// 偏向锁解锁操作  
void BiasedLocking::biased_locking_release(oop obj, JavaThread* thread) {  
    int thread_id = get_thread_id();  
    int* lock_word = get_header_address(obj);  
    if (*lock_word != 0) {  
        if ((*lock_word) & 0x2) {  
            // 如果是重量锁,直接将线程ID写入锁拥有者字段中,并标记为无锁状态  
            Atomic::cmpxchg((int)thread_id, lock_word, lock_word - 1);  
        } else {  
            // 如果是偏向锁,则将线程ID写入锁拥有者字段中,并标记为无锁状态,同时唤醒等待队列中的线程  
            Atomic::cmpxchg((int)thread_id, lock_word, lock_word - 1);  
            BiasedLocking::Biased_locking_unpark(thread);  
        }  
    } else {  
        // 如果已经是无锁状态,则直接返回,不做任何操作  
    }  
}

4. When to revoke

4.1. Arrive at the safe point

The cancellation of the bias lock needs to wait for the global safe point (safe point). At this time, all threads will be suspended, and then check whether the thread holding the bias lock is still alive. If the thread is not active, set the object header to the lock-free state; If the thread is still alive, it is necessary to traverse the stack holding the biased lock to check whether there are other object headers inconsistent with the object header, and if so, it is necessary to re-bias the thread. Finally wake up the suspended thread.

4.2. Other threads try to compete for biased locks

Also mentioned earlier. When another thread tries to acquire a biased lock, the thread holding the biased lock will release the lock, and the thread will not actively release the biased lock.

4.3. Recalculate hashcode

In the previous object header, we can see that when an object holds a biased lock, the object header does not store hashcode. So what about the hashcode of the originally stored object? In fact, after an object has calculated a consistent hash, it can no longer enter the biased lock state. However, when an object is currently in a biased lock state and receives a request to calculate its consistent hash code, its biased state will be revoked immediately, and the lock will expand into a heavyweight lock. In the implementation of the heavyweight lock, the object header points to the position of the heavyweight lock. There is a field in the ObjectMonitor class representing the heavyweight lock that can record the Mark Word in the non-locked state (the flag is "01"), which can naturally be stored The original hash code.

5. Summary

Biased locks can improve the performance of programs with synchronization but no competition, but it is also an optimization with a benefit trade-off (TradeOff), which means that it is not always beneficial to program operation. If most of the locks in the program are always accessed by multiple different threads, the bias pattern is redundant. On the premise of specific analysis of specific problems, sometimes using the parameter -XX:-UseBiasedLocking to disable biased lock optimization can improve performance.

Guess you like

Origin blog.csdn.net/p793049488/article/details/131389655