How is the synchronized bottom layer implemented?

Get into the habit of writing together! This is the third day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

Want to understand how synchronized works? We must first figure out how synchronized is achieved? The synchronized synchronization lock is implemented by the built-in Monitor monitor of the JVM, and the monitor is implemented by relying on the mutex of the operating system, so let's take a look at the monitor first.

monitor

A monitor is a concept or a mechanism that ensures that at any time, only one thread can execute code in a specified area.

A monitor is like a building. There is a special room in the building. This room can only be occupied by one thread at a time. A thread can monopolize all the data in the room from entering the room to leaving the room. Entering the building is called entering the monitor, entering the room is called acquiring the monitor, owning the room is called owning the monitor, and leaving the room is called releasing the monitor monitor), leaving the building is called exiting the monitor.

Strictly speaking, the concepts of monitors and locks are different, but they are also referred to each other in many places.

low-level implementation

Let's add a synchronized code block to the code to observe how it is implemented at the bytecode level? The sample code is as follows:

public class SynchronizedToMonitorExample {
    public static void main(String[] args) {
        int count = 0;
        synchronized (SynchronizedToMonitorExample.class) {
            for (int i = 0; i < 10; i++) {
                count++;
            }
        }
        System.out.println(count);
    }
}
复制代码

When we compile the above code into bytecode, the result is as follows: From the above result, we can see that there are a pair of monitorenter and monitorexit instructions in the main method, and their meanings are:

  • monitorenter: Indicates entering the monitor.
  • monitorexit: Indicates to exit the monitor.

It can be seen that synchronized is implemented by relying on the Monitor monitor.

Implementation process

在 Java 中,synchronized 是非公平锁,也是可以重入锁。 所谓的非公平锁是指,线程获取锁的顺序不是按照访问的顺序先来先到的,而是由线程自己竞争,随机获取到锁。 可重入锁指的是,一个线程获取到锁之后,可以重复得到该锁。这些内容是理解接下来内容的前置知识。 在 HotSpot 虚拟机中,Monitor 底层是由 C++实现的,它的实现对象是 ObjectMonitor,ObjectMonitor 结构体的实现如下:

ObjectMonitor::ObjectMonitor() {  
  _header       = NULL;  
  _count       = 0;  
  _waiters      = 0,  
  _recursions   = 0;       //线程的重入次数
  _object       = NULL;  
  _owner        = NULL;    //标识拥有该monitor的线程
  _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
  _WaitSetLock  = 0 ;  
  _Responsible  = NULL ;  
  _succ         = NULL ;  
  _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
  FreeNext      = NULL ;  
  _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
  _SpinFreq     = 0 ;  
  _SpinClock    = 0 ;  
  OwnerIsThread = 0 ;  
} 
复制代码

在以上代码中有几个关键的属性:

  • _count:记录该线程获取锁的次数(也就是前前后后,这个线程一共获取此锁多少次)。
  • _recursions:锁的重入次数。
  • _owner:The Owner 拥有者,是持有该 ObjectMonitor(监视器)对象的线程;
  • _EntryList:EntryList 监控集合,存放的是处于阻塞状态的线程队列,在多线程下,竞争失败的线程会进入 EntryList 队列。
  • _WaitSet:WaitSet 待授权集合,存放的是处于 wait 状态的线程队列,当线程执行了 wait() 方法之后,会进入 WaitSet 队列。

监视器执行的流程如下:

  1. 线程通过 CAS(对比并替换)尝试获取锁,如果获取成功,就将 _owner 字段设置为当前线程,说明当前线程已经持有锁,并将 _recursions 重入次数的属性 +1。如果获取失败则先通过自旋 CAS 尝试获取锁,如果还是失败则将当前线程放入到 EntryList 监控队列(阻塞)。
  2. 当拥有锁的线程执行了 wait 方法之后,线程释放锁,将 owner 变量恢复为 null 状态,同时将该线程放入 WaitSet 待授权队列中等待被唤醒。
  3. 当调用 notify 方法时,随机唤醒 WaitSet 队列中的某一个线程,当调用 notifyAll 时唤醒所有的 WaitSet 中的线程尝试获取锁。
  4. After the thread finishes executing and releases the lock, it will wake up all threads in the EntryList to try to acquire the lock.

The above is the execution flow of the monitor. The execution flow is shown in the following figure:image.png

Summarize

The synchronized synchronization lock is realized through the built-in Monitor monitor of the JVM, and the monitor is realized by relying on the mutex lock of the operating system. The execution flow of the JVM monitor is: the thread first tries to acquire the lock by spinning CAS, if the acquisition fails, it enters the EntrySet collection, and if the acquisition succeeds, it owns the lock. When the wait() method is called, the thread releases the lock and enters the WaitSet collection, and then tries to acquire the lock when other threads call the notify or notifyAll methods. After the lock is used, the threads in the EntrySet collection are notified to try to acquire the lock.

References

www.cnblogs.com/freelancy/p…

blog.csdn.net/qq_43783527/article/details/114669174

www.cnblogs.com/hongdada/p/…

It is up to oneself to judge right and wrong, to listen to others, and to count the gains and losses.

Collection of articles: gitee.com/mydb/interv…

Guess you like

Origin juejin.im/post/7086252383055020045