Synchronized是Java中的重量级锁,在我刚学Java多线程编程时,我只知道它的实现和monitor有关,但是synchronized和monitor的关系,以及monitor的本质究竟是什么,我并没有尝试理解,而是选择简单的略过。在最近的一段时间,由于实际的需要,我又把这个问题翻出来,Google了很多资料,整个实现的过程总算是弄懂了,为了以防遗忘,便整理成了这篇博客。 在本篇文章中,我将以class文件为突破口,试图解释Synchronized的实现原理。

从java代码的反汇编说起

很容易的想到,可以从 程序的行为 来了解synchronized的实现原理。但是在源代码层面,似乎看不出synchronized的实现原理。锁与不锁的区别,似乎仅仅只是有没有被synchronized修饰。不如把目光放到更加底层的汇编上,看看能不能找到突破口。 javap 是官方提供的*.class文件分解器,它能帮助我们获取*.class文件的汇编代码。具体用法可参考这里。 接下来我会使用javap命令对*.class文件进行反汇编。 编写文件Test.java:

public class Test { private int i = 0; public void addI_1(){ synchronized (this){ i++; } } public synchronized void addI_2(){ i++; }}复制代码

生成class文件,并获取对Test.class反汇编的结果:

javac Test.javajavap -v Test.class复制代码Classfile /Users/zhangkunwei/Desktop/Test.class Last modified Jul 13, 2018; size 453 bytes MD5 checksum ada74ec8231c64230d6ae133fee5dd16 Compiled from "Test.java" ... ... public void addI_1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: dup 6: getfield #2 // Field i:I 9: iconst_1 10: iadd 11: putfield #2 // Field i:I 14: aload_1 15: monitorexit 16: goto 24 19: astore_2 20: aload_1 21: monitorexit 22: aload_2 23: athrow 24: return ... ... public synchronized void addI_2(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return ... ...复制代码

通过反汇编结果,我们可以看到:

  • 进入被synchronized修饰的语句块时会执行 monitorenter ,离开时会执行 monitorexit
  • 相较于被synchronized修饰的语句块,被synchronized修饰的方法中没有指令 monitorentermonitorexit ,且flags中多了ACC_SYNCHRONIZED标志。 monitorentermonitorexit 指令是做什么的?同步语句块和同步方法的实现原理有何不同?遇事不决查文档,看看官方文档的解释。

monitorenter

DescriptionThe objectref must be of type reference .

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread > that executes monitorenter attempts to gain ownership of the monitor associated with objectref , as > > follows:

  • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor > and sets its entry count to one. The thread is then the owner of the monitor.
  • If the thread already owns the monitor associated with objectref , it reenters the monitor, incrementing its entry count.
  • If another thread already owns the monitor associated with objectref , the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

Notes

  • A monitorenter instruction may be used with one or more monitorexit instructions (§monitorexit) to implement a synchronized statement in the Java programming language (§3.14). The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods, although they can be used to provide equivalent locking semantics. Monitor entry on invocation of a synchronized method, and monitor exit on its return, are handled implicitly by the Java Virtual Machine's method invocation and return instructions, as if monitorenter and monitorexit were used.

简单翻译一下: 指令 monitorenter 的操作的必须是一个对象的引用,且其类型为引用。每一个对象都会有一个 monitor 与之关联,当且仅当 monitor 被(其他(线程)对象)持有时, monitor 会被锁上。其执行细节是,当一个线程尝试持有某个对象的 monitor 时:

  • 如果该对象的 monitor 中的 entry count ==0,则将 entry count 置1,并令该线程为 monitor 的持有者。
  • 如果该线程已经是该对象的 monitor 的持有者,那么重新进入 monitor ,并使得 entry count 自增一次。
  • 如果其他线程已经持有该对象的 monitor ,则该线程将会被阻塞,直到 monitor 中的 entry count ==0,然后重新尝试持有。 注意: monitorenter 必须与一个以上 monitorexit 配合使用来实现Java中的同步语句块。而同步方法却不是这样的:同步方法不使用 monitorentermonitorexit 来实现。当同步方法被调用时, Monitor 介入;当同步方法return时, Monitor 退出。这两个操作,都是被 JVM 隐式的handle的,就好像这两个指令被执行了一样。

monitorexit

Description

  • The objectref must be of type reference .
  • The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref .
  • The thread decrements the entry count of the monitor associated with objectref . If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

简单翻译一下: 指令 monitorenter 的操作的必须是一个对象的引用,且其类型为引用。并且:

  • 执行 monitorexit 的线程必须是 monitor 的持有者。
  • 执行 monitorexit 的线程让 monitorentry count 自减一次。如果最后 entry count ==0,这个线程就不再是 monitor 的持有者,意味着其他被阻塞线程都能够尝试持有 monitor

根据以上信息,上面的疑问得到了解释:

  1. monitorenter和 monitorexit 是做什么的? monitorenter 能“锁住”对象。当一个线程获取 monitor 的锁时,其他请求访问共享内存空间的线程无法取得访问权而被阻塞; monitorexit能“解锁”对象,唤醒因没有取得共享内存空间访问权而被阻塞的线程。
  2. 为什么一个 monitorenter 与多个 monitorexit 对应,是一对多,而不是一一对应? 一对多的原因,是为了保证:执行 monitorenter 指令,后面一定会有一个 monitorexit 指令被执行。上面的例子中,程序正常执行,在离开同步语句块时执行第一个 monitorexit ;Runtime期间程序抛出Exception或Error,而后执行第二个 monitorexit 以离开同步语句块。
  3. 为什么同步语句块和同步方法的反汇编代码略有不同? 同步语句块是使用 monitorentermonitorexit 实现的;而同步方法是 JVM 隐式处理的,效果与 monitorentermonitorexit 一样。并且,同步方法的flags也不一样,多了一个ACC_SYNCHRONIZED标志,这个标志是告诉 JVM :这个方法是一个同步方法,可以参考这里。

Monitor

在上一个部分,我们容易得出一个结论:synchronized的实现和 monitor 有关。 monitor 又是什么呢?从文档的描述可以看出, monitor 类似于操作系统中的 互斥量 这个概念:不同对象对共享内存空间的访问是互斥的。在 JVM ( Hotspot )中, monitor 是由 ObjectMonitor 实现,其主要的数据结构如下:

ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; //指向当前monitor的持有者  _WaitSet = NULL; //持有monitor后,调用的wait()的线程集合 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //尝试持有monitor失败后被阻塞的线程集合 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0;}复制代码

可以看出,我们可以

  • 通过修改_owner来指明 monitor 锁的拥有者;
  • 通过读取_EntryList来获取因获取锁失败而被阻塞的线程集合;
  • 通过读取_WaitSet来获取在获得锁后主动放弃锁的线程集合。

到这里,synchronized的实现原理已经基本理清楚了,但是还有一个未解决的疑问:线程是怎么知道 monitor 的地址的?线程只有知道它的地址,才能够访问它,然后才能与以上的分析联系上。答案是 monitor 的地址在Java对象头中。

Java对象头

在Java中,每一个对象的组成成分中都有一个Java对象头。通过对象头,我们可以获取对象的相关信息。 这是Java对象头的数据结构(32位虚拟机下):

其中的Mark Word,它是一个可变的数据结构,即它的数据结构是依情况而定的。下面是在对应的锁状态下,Mark Word的数据结构(32位虚拟机下):

synchronized是一个重量级锁,所以对应图中的重量级锁状态。其中有一个字段是:指向重量级锁的指针,共占用25+4+1=30bit,它的内容就是这个对象的引用所关联的 monitor 的地址。 线程可以通过Java对象头中的Mark Word字段,来获取 monitor的地址,以便获得锁。

回到最初的问题

synchronized的实现原理是什么?从上面的分析来看,答案已经显而易见了。当多个线程一起访问共享内存空间时,这些线程可以通过synchronized锁住 对象 的对象头中,根据Mark Word字段来访问该对象所关联的 monitor ,并尝试获取。当一个线程成功获取 monitor 后,其他与之竞争 monitor 持有权的线程将会被阻塞,并进入EntryList。当该线程操作完毕后,释放锁,因争用 monitor 失败而被阻塞的线程就会被唤醒,然后重复以上步骤。

写在最后

我发现其实大部分答案都可以从文档中得到,所以以后遇到问题还是要尝试从文档中找到答案。 本人水平有限,如果本文有错误,还望指正,谢谢~

需要java学习路线图的私信笔者“java”领取哦!另外喜欢这篇文章的可以给笔者点个赞,关注一下,每天都会分享Java相关文章!还有不定时的福利赠送,包括整理的学习资料,面试题,源码等~~

synchronized原理_浅谈synchronized的实现原理相关推荐

  1. 动态磅是怎么原理_浅谈动态地磅的原理及未来发展方向

    浅谈动态地磅的原理及未来发展方向: 文章介绍了动态地磅的结构和工作原理,针对动态地磅的分类做了全面的概述,分别对不同的动态地磅做了对比及详细的阐述,说明选择和使用动态地磅器的注意事项,凸显了轴组式动态 ...

  2. 伺服驱动器生产文件_浅谈伺服驱动器的工作原理

    原标题:浅谈伺服驱动器的工作原理 目前,主流的伺服驱动器均采用数字信号处理器(DSP)作为控制核心,可以实现比较复杂的控制算法,实现数字化.网络化和智能化.功率器件普遍采用以智能功率模块(IPM)为核 ...

  3. 相片打印机原理_浅谈喷墨打印机的照片输出(一)照片输出的需求与喷打在其中的地位和作用...

    浅谈喷墨打印机的照片输出(一) ---- 照片输出的需求与喷打在其中的地位和作用 自从有了照相机也就有了照片输出, 且不说冲印与打印的照片效果质量和原理方法到底如 何,单就打印机照片输出来说就分门别类 ...

  4. flink运行原理_浅谈Flink分布式运行时和数据流图的并行化

    本文将以WordCount的案例为主线,主要介绍Flink的设计和运行原理.关于Flink WordCount程序可以参考我之前的文章:读取Kafka实时数据流,实现Flink WordCount.阅 ...

  5. 1流式细胞术荧光比值计算_浅谈流式细胞仪的工作原理和应用

    流式细胞术(Flow Cytometry, FCM)是七十年代发展起来的一项高科学技术,80年代开始从基础研究发展到临床医学研究及疾病的诊断和治疗监测,我国在80年代初引进了第一台流式细胞仪. 它集光 ...

  6. https抓包_浅谈HTTPS抓包原理,为什么Charles能够抓取HTTPS报文?

    Charles作用其实相当于拦截器,当客户端和服务器通信时,Charles其实会先接收到服务器的证书,但是它会自己生成一个证书发送给客户端(不管是Web端或App应用),也就是说它不仅仅是拦截,甚至还 ...

  7. .net mysql limit 分页原理_浅谈MySQL分页Limit的性能问题

    MySQL的分页查询通常通过limit来实现.limit接收1或2个整数型参数,如果是2个参数,第一个是指定第一个返回记录行的偏移量,第二个是返回记录行的最大数目.初始记录行的偏移量是0.为了与Pos ...

  8. 00005在java结果输出_浅谈Java反序列化漏洞原理(案例未完善后续补充)

    摘要: 0005,这个16进制流基本上也意味者java反序列化的开始:(2)HTTP:必有rO0AB,其实这就是aced0005的base64编码的结果:以上意味着存在Java反序列化,可尝试构造pa ...

  9. pythonddos防御_浅谈拒绝服务攻击的原理与防御(7):用Python和C实现syn flood攻击...

    01 前言 以前做DDOS的实验都是用python来编写工具的,开始不会编写结构不会算校验和的时候就用scapy写,后来学会了报文结构开始自己构造各种报文,但是用python写成之后虽然是能实现基本功 ...

最新文章

  1. [ 一起学React系列 -- 10 ] i18n
  2. java 创建多线程_Java创建多线程
  3. string.Format出现异常输入的字符串格式有误的解决方法
  4. LoadRunner的Oracle计数器
  5. 记录——《C Primer Plus (第五版)》第八章编程练习第四题
  6. 在linux中用高斯09优化分子结构,高斯09的优化 - 量子化学 - Gaussian - 小木虫论坛-学术科研互动平台...
  7. 一文带你详尽剖析Miracast投屏开发和调试
  8. CFA Notes第一遍完成
  9. 固定资产管理有关的计算机知识,固定资产管理相关知识问答梳理(无形资产篇)...
  10. 【数据说·第十五期】如何对营业额数据进行分析,提升门店盈利能力?
  11. 讨论实现Windows资源管理器的简单方式
  12. halcon测量距离
  13. mysql5.7架设征途服务器,征途服务端架设详细教程
  14. 侯捷 C++ 课程系列视频 | 侯捷 STL 视频
  15. 在内核中创建文件 filp_open/sys_open
  16. 王道考研机试指南代码合集
  17. 卡壳卡壳(是读qia)
  18. bin/hive出错:Exception in thread main java.lang.RuntimeException: java.net.ConnectException: Call Fr
  19. [rtsp @ 0x55ba1dae9200] UDP timeout, retrying with TCP的解决办法
  20. Python学习教程(Python学习路线):Day14A-网络编程入门

热门文章

  1. 【Elasticsearch】数据预加载
  2. 【Elasticsearch】如何使用 Elasticsearch 6.2 搜索中文、日语和韩语文本 - 第 3 部分:语言检测工具
  3. 95-30-050-java.util-LinkedHashMap
  4. 【elasticsearch】elasticsearch 生命周期 resourceAlreadyExistsException
  5. 95-280-048-源码-资源管理-CPU
  6. kudu :impala 和 kuduClient 的选择
  7. mac电脑LC_CTYPE: cannot change locale (UTF-8): No such file or directory
  8. linux centos DNS 只有主服务器的实验
  9. tomcat——轻量级中间件学习
  10. error: ora-01034:oracle not available ora-27101:shared memory realm does not exist