当前位置: 首页 > 科技观察

面试惊喜:Synchronized底层是如何实现的?

时间:2023-03-23 01:56:23 科技观察

作者|雷哥来源|Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)想知道synchronized是如何工作的?你得先搞清楚synchronized是怎么实现的?Synchronized同步锁是通过JVM内置的Monitor监控器实现的,监控器是依赖操作系统的互斥锁Mutex实现的。接下来,我们先来看看显示器。监视器监视器是一种概念或机制,用于确保在任何时候只有一个线程可以在指定区域执行代码。监视器就像一座建筑物。大楼里有一间特别的房间。这个房间一次只能被一个线程占用。一个线程可以独占房间从进入房间到离开房间的所有数据。进入建筑物称为进入监视器,进入房间称为获得监视器,单独占据房间称为拥有监视器,离开房间称为释放监视器。monitor),离开建筑物称为退出monitor。严格来说,monitor和lock的概念是不同的,但是在很多地方,两者也是互相引用的。底层实现下面我们在代码中加入一个synchronized代码块,看看它在字节码层面是如何实现的。示例代码如下:publicclassSynchronizedToMonitorExample{publicstaticvoidmain(String[]args){intcount=0;同步(SynchronizedToMonitorExample.class){for(inti=0;i<10;i++){count++;}}System.out.println(计数);}}将上面的代码编译成字节码后,我们得到结果如下:从上面的结果我们可以看出在main方法中有一对monitorenter和monitorexit指令,它们的含义是:monitorenter:表示去进入监控。monitorexit:表示退出监听。可以看出synchronized是依赖Monitor监听实现的。执行过程在Java中,synchronized是一种非公平锁,也是一种可重入锁。所谓非公平锁,就是线程获取锁的顺序不是先到先得的访问顺序,而是线程本身随机竞争获取锁。可重入锁是指一个线程获得锁后,可以重复获得锁。这些内容是理解后面内容的前提知识。在HotSpot虚拟机中,Monitor的底层是用C++实现的,它的实现对象是ObjectMonitor。ObjectMonitor结构体的实现如下:ObjectMonitor::ObjectMonitor(){_header=NULL;_count=0;_waiters=0,_recursions=0;//线程重入次数_object=NULL;_owner=NULL;//标识拥有监视器的线程_WaitSet=NULL;//等待线程组成的双向循环链表,_WaitSet为第一个节点_WaitSetLock=0;_Responsible=NULL;_succ=NULL;_cxq=NULL;//多线程竞争锁进入时的单向链表FreeNext=NULL;_EntryList=NULL;一个节点_SpinFreq=0;_自旋时钟=0;所有者是线程=0;}上面代码中有几个关键属性:_count:记录线程获得锁的次数(即线程总共获得了多少次锁)。_recursions:锁的重入次数。_owner:TheOwner的所有者,也就是持有ObjectMonitor(监控)对象的线程;_EntryList:EntryList监控集合,存放阻塞状态的线程队列。多线程下,竞争失败的线程会进入EntryList队列。_WaitSet:WaitSet等待授权集合,存放等待状态的线程队列。当线程执行完wait()方法后,就会进入WaitSet队列。monitor执行过程如下:线程尝试通过CAS(对比替换)获取锁。如果获取成功,_owner字段设置为当前线程,表示当前线程已经持有锁,重入次数的_recursions属性+1。如果获取失败,先尝试通过自旋CAS获取锁,如果还是失败,则将当前线程放入EntryList监听队列(阻塞)。当拥有锁的线程执行wait方法时,线程释放锁,将owner变量恢复为null状态,并将线程放入WaitSet等待授权队列等待被唤醒。当调用notify方法时,随机唤醒WaitSet队列中的一个线程,当调用notifyAll时,唤醒WaitSet中的所有线程尝试获取锁。线程执行完释放锁后,EntryList中的所有线程都会被唤醒尝试获取锁。以上就是monitor的执行过程。执行过程如下图所示:总结synchronized同步锁是通过JVM内置的Monitor监视器来实现的,而监视器是依靠操作系统的互斥锁Mutex来实现的。JVMmonitor的执行流程是:线程首先通过自旋CAS尝试获取锁,获取失败则进入EntrySet集合,获取成功则拥有锁。当wait()方法被调用时,线程释放锁并进入WaitSet集合,然后在其他线程调用notify或notifyAll方法时尝试获取锁。锁用完后,会通知EntrySet集合中的线程尝试获取锁。参考www.cnblogs.com/freelancy/p/15625602.htmlblog.csdn.net/qq_43783527/article/details/114669174www.cnblogs.com/hongdada/p/14513036.html。