想会用synchronized锁,先掌握底层核心原理
摘要:synchronized锁修饰方法和代码块时底层实现上是一样的,但是在修饰方法时,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的。
本文分享自华为云社区《Synchronized底层核心原理》,作者: 小威要向诸佬学习呀 。
synchronized锁用于同步实例方法,同步静态方法和同步代码块。自从Java1.6开始,就对synchronized锁进行了很多方面的优化。对其引入了偏向锁,轻量级锁,适应性自旋锁,锁粗化,锁消除等各种技术方面的优化。
synchronized锁是基于monitor锁实现的,因此在讲解synchronized锁之前,有必要了解一下monitor锁。
monitor锁的原理
monitor,在中文中有监视器的意思,当创建对象时,每一个创建出来的对象都会关联一个monitor对象,对于一个java对象,当拿到这个monitor对象时,这个monitor对象就会处于锁定的状态,其他对象不会再获取,synchronized锁的本质就是基于进入和退出monitor对象实现的同步方法和同步代码块。
这里首先解释一下wait,notify,notifyAll等方法的各个作用:
wait方法会让进入object监视器的线程进入到WaitSet集合中等待;
notify方法会使在object上正在WaitSet集合上等待的线程中挑一个唤醒线程;
notifyAll方法会让正在WaitSet集合中等待的线程全部唤醒。
而对于monitor,它是基于ObjectMonitor实现的,ObjectMonitor的主要数据结构包括:
owner:owner原本的值为null,它用来指向获取到ObjectMonitor对象的线程。当一个线程获取到ObjectMonitor对象时,这个ObjectMonitor对象就会存储在当前对象的对象头中的Mark
Word中。
WaitSet,这个是ObjectMonitor中的一个集合,同时WaitSet与wait()方法有关。当Owner线程发现条件不满足时,会调用wait方法,使线程进入WaitSet集合中变为WAITING状态。
EntryList,也是ObjectMonitor中的一个集合,同时EntryList与notify(),notifyAll()方法有关。WAITING状态下的线程会在Owner线程调用notify()或notifyAll()等方法时唤醒,但是唤醒之后并不代表着线程会立即拿到锁资源,而是需要进入EntryList集合中进行竞争。
模拟多线程情况下,同时访问一个被synchronized锁修饰方法时,在JVM底层中的流程如下·:
- 线程进入EntryList集合时,如果某个线程获取到monitor对象时,这个线程会进入owner中,同时会把monitor对象中的owner变量复制为当前的线程(拿到monitor对象的这个),并且会把monitor对象中的count变量值+1。
- 如果线程调用wait方法,当前的线程就会释放拿到的monitor对象,并且会把monitor对象中的owner变量值设为null,并且count的值-1。最后,当前线程会进入到WaitSet集合中等待,等候再次被唤醒。
- 如果是获得monitor对象的线程执行任务完成后,也会进行上面的一系列操作,但不会到WaitSet集合中等待了,因为任务已经执行完了。
synchronized修饰方法
前面说到synchronized锁是基于monitor锁实现的。当synchronized锁修饰方法时,被此锁修饰的方法会比普通方法的常量池中多一个ACC_SYNCHRONIZED标识符。当线程调用了被synchronized锁修饰的方法时,会检查方法中是否设置了此标识符。
如果设置了ACC_SYNCHRONIZED标识符,那么当前的线程会首先获取monitor锁对象,然后执行同步代码中的方法,完成后会释放monitor对象。当然,在多线程情况下,只有一个线程能够获取此monitor对象,并且在该线程释放monitor对象之前,其他线程无法获取此monitor对象。因此在同一时刻,只能有一个线程拿到相同对象的synchronized锁资源。
而当synchronized锁修饰代码块时,与synchronized修饰方法略有不同,接下来详细讲解synchronized修饰代码块的情况。
synchronized修饰代码块
当synchronized锁修饰代码块时,synchronized关键字会被编译成monitorenter和monitorexit两条指令,其中,monitorenter会放在代码块的前面,而monitorexit会放在代码块的后面。
对于monitorenter指令:
每个对象都拥有一个monitor,当monitor被占用时,就会处于锁定状态,线程执行monitorenter指令时会获取monitor的所有权。
当monitor计数为0时,说明该monitor还未被锁定,此时线程会进入monitor并将monitor的计数器设为1,并且该线程就是monitor的所有者。
如果此线程已经获取到了monitor锁,再重新进入monitor锁的话,那么会将计时器count的值加1。
如果有线程已经占用了monitor锁,此时有其他的线程来获取锁,那么此线程将进入阻塞状态,待monitor的计时器count变为0,这个线程才会获取到monitor锁。
对于monitorexit指令:
首先,只有拿到了monitor锁对象的线程才会执行monitorexit指令。
其次就是,在执行monitorexit指令时,计时器count的值会减1,当count的值减到0时,当前的线程才会退出monitor,此时的线程不再是monitor的所有者,当然执行后,其他线程可以获取当前monitor锁的所有权。
通过对简单代码进行反编译来举例:
public class SynchronizedTest { public void synchronize(){ synchronized (this){ System.out.println("hello world"); } } }
执行 javap -c SynchronizedTest.class指令得到以下字节码:
public class Synchronized.SynchronizedTest { public Synchronized.SynchronizedTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void synchronize(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello world 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any }
由上述编码可以看出,在synchronized修饰的代码块中,存在有monitorenter指令和monitorexit指令。
synchronized锁总结
因此,由以上可以得出,synchronized锁修饰方法和代码块时底层实现上是一样的,但是在修饰方法时,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
OpenHarmony移植案例: build lite源码分析之hb命令__entry__.py
摘要:本文介绍了build lite 轻量级编译构建系统hb命令的源码,主要分析了_\entry__.py文件。 本文分享自华为云社区《移植案例与原理 - build lite源码分析 之 hb命令__entry__.py》,作者:zhushy 。 hb命令可以通过python pip包管理器进行安装,应该是OpenHarmony Build的缩写,在python包名称是ohos-build。hb作为编译构建子系统提供的命令行,用于编译构建产品、芯片厂商组件或者单个组件。我们来学习hb命令行工具的源码,本文主要分析下文件openharmony/build/lite/hb/__entry__.py。 1、find_top()函数 find_top()函数用于获取OpenHarmony源代码根目录,之前的系列文章分析过。代码也较简单,不再赘述。 def find_top(): cur_dir = os.getcwd() while cur_dir != "/": hb_internal = os.path.join(cur_dir, 'build/lite/hb_internal')...
- 下一篇
用新服务器从零开始部署 DolphinDB
本文主要介绍在新服务器部署 DolphinDB 时需要注意哪些系统配置,以及如何选择 DolphinDB 部署方式以符合业务需求。合适的系统配置可以提高 DolphinDB 系统的稳定性和可维护性,而合适的部署方式可以提高业务执行的效率。 1. 操作系统配置 本文以 CentOS 7.9.2009 为例,介绍 DolphinDB 相关的系统配置方法。 1.1 平台要求与推荐 1.1.1 支持的平台 平台 处理器架构 是否支持 Linux x86 是 arm 是 龙芯 是 Windows x86 是 Mac - 否 BSD - 否 在 Linux 系统使用 DolphinDB 要求内核版本为 Linux 2.6.19 或以上,推荐使用 CentOS 7 稳定版。 1.1.2 依赖软件 DolphinDB 依赖 gcc 4.8.5 或以上版本。以在 CentOS 7 稳定版上安装为例: # yum install -y gcc 1.1.3 推荐硬件配置 需要为 DolphinDB 元数据,redo log 以及数据实体配置不同的硬盘,以优化系统性能。 元数据和 redo log:建议配一块...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 2048小游戏-低调大师作品
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G