Multithreading from entry to advanced (11) ---sync lock implementation principle

One, the characteristics of Synchronized

In high-concurrency programming, thread safety is a topic that needs to be focused on, and there are two aspects that cause thread safety:

  • Need to have shared resources or critical resources
  • Simultaneous operation of multiple threads

If the above two conditions are met, there may be thread safety problems. The solution is very simple, which is to control the thread that operates this shared variable at every moment, which is a mutex lock. Synchronized is a kind of mutual exclusion lock. Synchronized can ensure that only one thread enters the locked critical resource at the same time. Synchronized also has the following characteristics:

  • Atomicity: This is an important prerequisite for Synchronized to ensure thread safety. The atomic operations either succeed together or fail completely, that is, the intermediate operations are indivisible. For example, the code of i++, in fact, the underlying bytecode is divided into three Reading, counting, and assigning are performed in two steps. If the sync modification is added, these three operations are one and indivisible.volatile和sync的重要区别之一就是sync具有原子性和volatile不具有原子性

  • Visibility: Visibility means that operations on any shared variable in the code locked by sync are visible to other threads. This also involves the thread model of Java. JMM (java thread model) stipulates that all shared variables must exist. In the main memory, and each thread has a work area, the operation of the shared variable by the thread must be carried out in the work area, that is, copy the shared variable in the main memory to its own work area, and then modify it again. Put back in the work area, the data exchange between each thread must be completed through the main memory, and data cannot be transferred between different work areas

  • Order: In order to improve the efficiency of the program, Java will reorder the instruction program, but under high concurrency, this reordering is likely to change the running result of the program, so sync guarantees order, that is, not letting it. JVM performs instruction reordering

  • Reentrancy: A thread that owns the current lock can also repeatedly apply for the current lock.synchronized和ReentrantLock都是可重入锁

Synchronized has always been called a heavyweight lock, but after jdk1.6, in order to reduce the performance consumption of locks and release locks, biased locks and lightweight locks were introduced

And the storage structure and upgrade process of the lock

Second, the principle of Synchronized lock synchronization

1. Lock the synchronization code block

public class SynchronizedDemo {
    
    
    Object lock = new Object();
    public void test1(){
    
    
        synchronized (lock){
    
    };
    }
}

To see how the bottom layer of the JVM is implemented, the most direct way is to use the javap instruction to view the bytecode file

  public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field lock:Ljava/lang/Object;
         4: dup
         5: astore_1	
         6: monitorenter		// !!! 关注这里
         7: aload_1
         8: monitorexit			// !!! 关注这里
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit
        15: aload_2
        16: athrow
        17: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 6: 0
        line 7: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class cn/java/SynchronizedDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

From the results of decompilation, there are monitorenter and monitorexit before and after the synchronization method. Looking at these two words, you can roughly know that there are two operations to enter the monitor and exit the monitor. As for what the monitor is, I will continue to talk about it later. Now it can be understood as a lock, and this lock is carried by each object as soon as it is new

monitorenter的过程:

  • If the monitor entry number of an object is 0, then the thread enters the monitor, and the monitor entry number is increased by 1, then the thread is the user of the monitor
  • If a thread already holds the monitor and just wants to reacquire it, then the monitor entry number is increased by one again, which is the realization principle of reentrancy
  • If a thread finds that the number of entries in the monitor is not 0, that is, the monitor has been occupied by other threads, then the thread will block until the number of entries in the monitor is 0, and obtain the ownership of the monitor again

monitorexit的过程:

  • The thread that executes the monitorexit must be the holder of the monitor. When the instruction is executed, the entry number of the monitor is reduced by 1. If the number of the monitor is 0, then the thread will exit the monitor and no longer hold the right to use the monitor. At this time, other threads Can obtain the right to use the monitor

2. Lock the synchronization method

public class SynchronizedDemo {
    
    
    synchronized public void test2(){
    
    }
}

The same use javap to decompile the code


  public synchronized void test2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 11: 0

From the analysis of the above decompilation results, monitorenter and monitorexit are not used, but there is an additional tag ACC_SYNCHRONIZED in the constant pool, which is described in the official java virtual machine specification document directly.

A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the run-time constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions (§2.11.10).

The synchronization method is usually not implemented using monitorenter and monitorexit. On the contrary, it is only distinguished by the ACC_SYNCHRONIZED flag in the runtime constant pool, which is checked by the method call instruction

You can continue to see chapter 2.11.10, the official document describes it in detail. Method-level synchronization is performed implicitly. When the method that sets the ACC_SYNCHRONIZED flag is called, the execution thread will acquire the monitor. After the acquisition is successful, the method body can be executed, and the monitor is released after the method is executed.

In other words, in essence, the synchronization method and synchronization code block locked by Synchronized are implemented by monitor, but the synchronization method is only an implicit execution.

3.java object header

Without special instructions, the following are all Hostop's virtual machines. The layout of objects in virtual machine memory can be divided into three parts:

  • Object header
  • Instance data
  • Data input

Related to Synchronized is the data in the object header. The padding data is not a necessary part, but the starting address of the object must be an integer multiple of 8 bytes. The padding data is only for byte alignment.

The main object header structure is composed of Mark Word(标记字段), , Class Metadata Address(类型指针)the Metadata the Address Class object type stored in the data pointer, the virtual machine is determined by which object is an instance of this pointer, and if the object is an array, then the additional storage will be a Array lengthvalue of the length of the array

length content Description
32/64bit Mark Word Store the hashcode or lock information of the object
32/64bit Class Metadata Address Pointer to store object type data
32/32bit Array length The length of the array

In a 64-bit virtual machine, Mark Word is 64-bit, and its storage structure is as follows

img

According to the different lock flags, the data stored in Mark Word will change accordingly. For example:

img

Among them, the bias lock, lightweight lock, and heavyweight lock will be involved when we talk about the optimization of locks. At present, we only introduce what data can be stored in the object header of Mark Word.

4.monitor

When Synchronized locks a code block or method, a monitor is involved. According to Java, everything is an object. A monitor can be understood as an object or a tool. This tool can guarantee the synchronization mechanism of the Synchronized lock. All Java objects have their own monitor object

The source code of the monitor can be found in the source code of the virtual machine:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

Mainly to pay attention to the above two containers waitSet and EntryList are used to save the thread that is currently waiting for some reason, _owner points to the thread holding the current monitor. When multiple threads want to acquire a lock at the same time, the process is as follows:

  1. The thread enters the _EntryList collection and waits. When the thread gets the monitor' of the object, it sets the owner variable in the monitor to the current thread and sets the count variable++
  2. When the thread calls the wait() method, it releases the holding right of the current monitor and restores the owner to null, the count is reduced by 1, and the thread enters the waitSet collection and waits to be awakened
  3. When the thread with the current usage rights finishes executing the code and releases the current lock, does the owner return to null? The count is reduced by 1, so that other threads can acquire

Third, the optimization of the lock

Releasing locks and acquiring lock settings involves switching between the kernel mode and user mode at the bottom of the operating system, and this switching is very resource intensive. In order to reduce the performance consumption of acquiring and releasing locks, Jdk1.6 introduces These locks are "biased locks" and "lightweight locks". These locks are actually different states in different application scenarios using Synchronized. The lock flag in Mark Word also saves the current state of the lock.

image-20210314161134325

The upgrade process of the lock is shown in the figure above. It should be noted that this upgrade process is irreversible, that is, it can only be upgraded in the direction of the arrow. The general process is

image-20210314162037441

1. Bias lock

Object head is made Mark Word(标记字段), Class Metadata Address(类型指针)consisting of, when a thread holds the monitor of this object, it will be Mark Word mark field is marked as read lock 01, lock into bias mode

image-20210314162914542

This mode only allows the same thread to acquire a lock at a lower cost, that is to say, when used in a single-threaded mode, if the lock is closed in a high concurrency environment, multiple threads start to compete for the lock, and the lock is upgraded to lightweight lock

2. Lightweight lock

Before competing for the lock, the JVM will create a space for storing the lock record (Lock Record) in the stack frame of the current thread, and copy the Mark Word in the object header to the lock record. When a thread tries to acquire a lock, use CAS to replace the Mark Word in the object header with a pointer to the lock record. If it succeeds, acquire the lock. If it fails, it means other resources compete for the lock, and the current thread tries to use spin to acquire the lock.

image-20210314164719709

3. Spin lock

Switching between user mode and kernel mode consumes a lot of resources. In order to reduce this consumption, JVM introduces a spin lock. Once resources are not obtained, it will spin and wait, instead of directly suspending and blocking, although this will consume CPU Resource, but this consumption is smaller than the switch between user mode and kernel mode, but if the thread has not been able to obtain the lock, it cannot spin forever. The number of spins needs to be limited. When the number of spins exceeds 10 After this time, the lock is upgraded to a weight lock

img

4. Summary

The so-called biased lock means favoritism. I favor the thread that gets the lock first. This lock is suitable for synchronization scenarios with only one thread, because in a single-threaded environment, there will be no threads to grab the lock, so the virtual machine is also There is no need to lock and unlock operations;

When there is another thread to acquire the lock, the biased lock is declared over, but at this time, it is not directly calling the statement of the system kernel state to lock, but waiting at the CPU code level, hoping that the lock can be acquired in this way, this is spin lock;

When the lock has not been acquired after spinning for more than 10 times, you can no longer wait to waste CPU resources, and upgrade the lock to a weight lock, temporarily suspending threads that want to compete for the lock.

Favoring the first thread to obtain the lock, this lock is suitable for synchronization scenarios with only one thread, because in a single-threaded environment, there will be no threads to grab the lock, and there is no need for the virtual machine to perform locking and unlocking operations;

When there is another thread to acquire the lock, the biased lock is declared over, but at this time, it is not directly calling the statement of the system kernel state to lock, but waiting at the CPU code level, hoping that the lock can be acquired in this way, this is spin lock;

When the lock has not been acquired after spinning for more than 10 times, you can no longer wait to waste CPU resources, and upgrade the lock to a weight lock, temporarily suspending threads that want to compete for the lock.

Guess you like

Origin blog.csdn.net/weixin_44706647/article/details/114945983