专栏首页HUC思梦的java专栏synchronized底层揭秘

synchronized底层揭秘

前言

上篇文章我们从硬件级别探索,对可见性和有序性的认识上升了一个高度,却迟迟没有介绍原子性的解决方案。

今天我们就来聊一聊原子性的解决方案,

引入锁机制,除了可以保证原子性,同时也可以保证可见性和有序性。

相信小伙伴们对于synchronized互斥锁一定很熟悉,但是你懂它的实现原理吗,今天就让我们一起来揭开它的神秘面纱吧。

synchronized的原子性

首先我们来看一下synchronized是怎么保证原子性的。

其实往最简单了解释,还是比较容易理解的。synchronized加锁主要靠的是monitor,monitor在java里可以理解成一个监视器,在操作系统里它又被称为管程。

简单的模型如下图:

当我们的程序通过synchronized锁定一个对象的时候,这个对象会关联一个monitor,获取锁时会对monitor中的计数器进行+1操作,释放锁的时候进行-1操作,同时也是支持可重入的,同一个线程再次获取该对象的锁,计数器就再+1。

如果计数器为0就代表完全释放了锁,其他线程可以获取锁。

如果线程调用了wait方法,会释放锁资源,同时把线程放入waitset中,等待notifyall方法唤醒,唤醒后重新开始竞争锁资源。

这就是sychronized锁的最简单的解释,我们当然不会满足于此,接下来我们继续深入研究一下

先看一段代码:

MyLock lock = new MyLock();//一个自定义的锁对象
sychronized(lock){
//...
}

java的对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象头包含Mark Word(包含hashcode、锁数据、GC信息等)和Class MetaData Address(指向Class对象的指针)。

实例数据就是我们在对象里存放的那些数据。

java要求对象大小为8字节的整数倍,对齐填充就是用来填充字节的,没有其他意义。

Mark Word会指向一个monitor,这个monitor是C++实现的一个Object Monitor对象,首先线程在获取锁时,先进入到entry list中,然后通过CAS对count计数器进行+1操作,如果+1成功代表获取到锁,此时就会把该线程的信息放入owner中,owner就是用来存储当前获取到锁的线程的。整体结构如图所示:

sychronized的可见性

在说可见性之前,我们先引入两个概念:Store屏障和Load屏障

Store屏障就是强制CPU执行flush操作,Load屏障就是强制CPU执行refresh操作。

flush和refresh我们上篇文章已经说过,这里就不再解释了。

那sychronized是如何实现可见性的呢,其实就是利用了内存屏障。如下:

sychronized(this){
   // monitorenter
   // Load内存屏障
   //...  
}
//monitorexit
//Store内存屏障

sychronized的有序性

同样在说有序性之前引入两个新的内存屏障:Acquire屏障和Release屏障

Acquire屏障可以禁止读操作和其他读写操作之间发生指令重排,Release屏障可以禁止写操作和其他读写操作之间发生指令重排。

那sychronized是如何实现有序性的呢,其实就是利用了这两个内存屏障。如下:

sychronized(this){
   // monitorenter
   // Load内存屏障
   // Acquire内存屏障
   //...  
   //Release内存屏障
}
 //monitorexit
 //Store内存屏障

需要注意的是Acquire屏障和Release屏障保证的是sychronized内部的代码不会与外部的代码之间发生指令重排,内部的代码自己还是可能发生指令重排的。

sychronized的锁优化

jdk1.6后jvm对sychronized进行了锁优化,这部分我们做个概念了解就可以了。

1.锁消除

锁消除是JIT编译器对sychronized的优化,在编译的时候会通过逃逸分析技术,来分析锁对象。如果只有一个线程来加锁和解锁,没有锁竞争,那就没有必要加锁,会去掉monitorenter和monitorexit指令。

2.锁粗化

这个意思是,如果有多个连续的加锁释放锁操作,那么编译后会变成一把锁。

例如

sychronized(this){}

sychronized(this){}

sychronized(this){}

连着三个加锁操作,编译后会变成一个。

3.偏向锁

偏向锁主要是为了减少monitorenter和monitorexit指令的CAS操作,减少开销,如果认为当前锁大概率只有一个线程来竞争,那么就会给这个锁维护好一个偏好Bias,之后该线程加锁和释放锁都通过这个Bias来执行,不需要去执行CAS了。

但是如果发现有其他线程来竞争锁,就会收回之前分配好的偏好。

4.轻量级锁

如果偏向锁没能实现,也就是说有多个线程竞争锁,那么就会采用轻量级锁。

其实就是将对象里的轻量级锁指针指向一个已经获取了锁的线程,然后判断一下是不是自己加的锁,如果是就直接执行,如果不是说明有其他线程加了锁,就会升级为重量级锁,重量级锁流程我们上文中介绍原子性的时候已经说过了。

5.适应性自旋锁

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复的花费可能会让系统得不偿失。为了让当前线程“稍等一下”,我们需让当前线程进行自旋。

如果在自旋完成后前面锁同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免了切换线程的开销。这就是自旋锁。

适应性自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

总结

到这里,有关synchronized的底层实现我们基本上已经聊完了。

使用锁来保证原子性,使用内存屏障来保证可见性和有序性。

同时jvm又对sychronized做了一些优化。

相信小伙伴们理解了本文的内容,会收获颇丰。

那我们下次再见。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 揭秘 Node.js 底层架构

    2009 年 Ryan Dahl 在JSConf EU大会上推出了 Node.js,最初是希望能够通过异步模型突破传统 Web 服务器的高并发瓶颈,之后愈渐发展...

    ayqy贾杰
  • HTTP认证的底层技术简析与揭秘

    写在前面的话 HTTP认证实现的基础是Web服务器与浏览器之间能够安全地交换类似用户名和密码这样的用户凭证,我们也可以把HTTP认证当作是摘要验证(Digest...

    FB客服
  • 死磕Synchronized底层实现

    关于synchronized的底层实现,网上有很多文章了。但是很多文章要么作者根本没看代码,仅仅是根据网上其他文章总结、照搬而成,难免有些错误;要么很多点都是一...

    Java团长
  • 死磕Synchronized底层实现

    多线程的东西很多,也很有意思,所以我最近的重心可能都是多线程的方向去靠了,不知道大家喜欢否?

    敖丙
  • 死磕Synchronized底层实现

    关于synchronized的底层实现,网上有很多文章了。但是很多文章要么作者根本没看代码,仅仅是根据网上其他文章总结、照搬而成,难免有些错误;要么很多点都是一...

    java思维导图
  • synchronized 关键字底层原理

    通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 javac Synchronize...

    happyJared
  • 深度揭秘垃圾回收底层,这次让你彻底弄懂她

    我们知道手动管理内存意味着自由、精细化地掌控,但是却极度依赖于开发人员的水平和细心程度。

    why技术
  • synchronized底层是怎么实现的?

    面试的时候有被问到,synchronized底层是怎么实现的,回答的比较浅,面试官也不是太满意,所以觉得要好好总结一下,啃啃这个硬骨头。

    纪莫
  • C++反汇编与逆向分析技术揭秘

    《C++反汇编与逆向分析技术揭秘》从介绍调试工具开始,到语言特性的分析,反汇编代码的重建等,再到逆向分析技术应用,内容逐步深入。软件分析技术重在方法,所以《C+...

    用户3157710
  • 彻底揭秘keep-alive原理

    用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(或选中)状态。keep-a...

    前端黑板报
  • 死磕Synchronized底层实现--偏向锁

    偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章的基础下阅读本文。

    用户1516716
  • 死磕synchronized关键字底层原理

    作为Java程序员,我们都知道在多线程的情况下,为了保证线程安全,经常会使用synchronized和Lock锁。Lock锁之前写过一篇《不得不学的AQS》,已...

    java技术爱好者
  • synchronized底层实现知多少?synchronized加锁还会升级?

    线程2将count减到了97,线程3、线程1在某一刻也做了count--,但是结果却也是97,说明他们在做count--的时候并不知道有别的线程也操作了coun...

    行百里er
  • 分享基于 websocket 网页端聊天室

    有一个月没有写博客了,也是因为年前需求多、回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!!

    Krry
  • java 开发 websocket 网页端聊天室

    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

    Krry
  • 死磕Synchronized底层实现--轻量级锁

    另外轻量级锁的背景和基本流程在概论中已有讲解。强烈建议在看过两篇文章的基础下阅读本文。

    用户1516716
  • 天天用Synchronized,底层原理是个啥?

    接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了 Synchronized 的使用方式不同以外,其他基本保持一致)。

    哲洛不闹
  • 天天用Synchronized,底层原理是个啥?

    https://www.cnblogs.com/paddix/p/5367116.html

    Java技术栈
  • 死磕Synchronized底层实现--重量级锁

    本系列文章将对HotSpot的synchronized锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研...

    李红

扫码关注云+社区

领取腾讯云代金券