1.Moment.js、Day.js、Miment,日期时间库怎么选?2.Parallax.js:让智能设备视差效果更智能、更自然3.Lottie动画全攻略:硬核还原100%页面动画效果4.用particles.js,让你的网站背景特效瞬间脱颖而出,惊艳所有人5.字节跳动出大招!IconPark图标库,自定义图标,好用到停不下来!6.比肩Element和Ant Design,PrimeVue同样优秀,你不可不知的UI框架新星!7.ChartCube图表快速上手指南,轻松打造专业图表,简单到不可思议!8.Vue地图开发新利器:Vue Baidu Map,轻松对接,效率翻倍!9.尤雨溪都在推荐的Naive UI,Vue组件库的新选择,好用到爆!10.【uni-app必备】uView UI框架,多端开发神器,让开发更简单、更高效!11.Driver.js:轻量级用户引导插件,小而美的界面,让用户体验飞起来!12.Ionicons图标库: 让网页栩栩生辉,Ionic Framework的经典之作,图标库新标杆!13.Clipboard.js:一个被157317个项目疯抢的JS开源库14.探“锁”源头:synchronized、偏向锁与锁膨胀的秘密!
15.解"锁"疑惑:偏向锁为什么不是锁?锁升级又是什么?何时禁用偏向锁和轻量级锁?重量级锁怎么回事?
16.秒懂!5分钟图解 Elasticsearch 搜索原理,快速掌握全文检索技术!17.2025年GitHub Copilot免费激活,周年庆典福利大放送!18.【深度解析】DDD领域驱动设计,分层架构秘籍大公开!让你的设计更上一层楼!19.AI编程工具怎么选?GitHub Copilot、AI Assistant与Cursor,谁是你的最佳拍档?20.【免费福利】腾讯云+DeepSeek:DeepSeek-V3/R1免费,羊毛党速来大家好,我是程序视点的小二哥!今天我们继续来聊聊Java中的锁!
程序视点:探“锁”源头:synchronized、偏向锁与锁膨胀的秘密!0 赞同 · 0 评论文章
-
synchronized怎么用?
-
锁是什么?
-
偏向锁是什么?
-
锁如何升级?何为膨胀?
-
自旋锁何解?
-
互斥锁怎么来的?
-
何时要禁用偏向锁和轻量级锁?
带着上面疑问,我们一起来解“锁”疑惑!以下是第二篇文章来讲,方便大家记忆!欢迎持续关注【程序视点】,这样就不会错过之后的精彩内容啦!
前言
前面说过,在大多数情况下,总是同一个线程去访问同步块代码,基于这样一个假设,引入了偏向锁,只需要用一个CAS操作和简单地判断比较,就可以让一个线程持续地拥有一个锁。 也正因为此假设,在Jdk1.6中,偏向锁的开关是默认开启的,适用于只有一个线程访问同步块的场景。
但一旦出现竞争,也即有另外一个线程也要来访问这一段代码,偏向锁就不适用于这种场景了。
锁膨胀
如果两个线程都是活跃的,会发生竞争,此时偏向锁就会发生升级,也就是我们常常听到的锁膨胀。 偏向锁会膨胀成轻量级锁(lightweight locking)。
锁撤销
偏向锁有一个不好的点就是,一旦出现多线程竞争,需要升级成轻量级锁,是有可能需要先做出锁撤销的操作。
而锁撤销的操作,相对来说,开销就会比较大,其步骤如下:
-
在一个安全点停止拥有锁的线程,就跟开始做GC操作一样。
-
遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状态。
-
唤醒当前线程,将当前锁升级成轻量级锁。
轻量级锁
而本质上呢,其实就是锁对象头中的Markword内容又要发生变化了。
下面先简单地描述其膨胀的步骤:
-
线程在自己的栈桢中创建锁记录 LockRecord
-
将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中
-
将锁记录中的Owner指针指向锁对象
-
将锁对象的对象头的MarkWord替换为指向锁记录的指针。
同样,我们还是利用Java开发人员提供的一张图来描述此步骤:


可以根据上面两图来印证上面几个步骤,但在这里,其实对象的Markword其实也是发生了变化的,其现在的内容结构如下:
说到这里,我们又通过偏向锁引入了轻量级锁的概念,那么轻量级锁是怎么个轻量级法,它具体的实现又是怎么样的呢?
就像偏向锁的前提,是同步代码块在大多数情况下只有同一个线程访问的时候。
而轻量级锁的前提则是,线程在同步代码块里面的操作非常快,获取锁之后,很快就结束操作,然后将锁释放出来。
但是不管再怎么快,一旦一个线程获得锁了,那么另一个线程同时也来访问这段代码时,怎么办呢?这就涉及到我们下面所说的锁自旋的概念了。
自旋锁/自适应自旋锁
来到轻量级锁,其实轻量级的叙述就来自于自旋的概念。
因为前提是线程在临界区的操作非常快,所以它会非常快速地释放锁,所以只要让另外一个线程在那里地循环等待,然后当锁被释放时,它马上就能够获得锁,然后进入临界区执行,然后马上又释放锁,让给另外一个线程。
所谓自旋,就是线程在原地空循环地等待,不阻塞,但它是消耗CPU的。
所以对于轻量级锁,它也有其限制所在:
-
因为消耗CPU,所以自旋的次数是有限的,如果自旋到达一定的次数之后,还获取不到锁,那这种自旋也就无意义。但在上述的前提下,这种自旋的次数还是比较少的(经验数据)。当然,一开始的自旋次数都是固定的,但是在经验代码中,获得锁的线程通常能够马上再获得锁,所以又引入了自适应的自旋,即根据上次获得锁的情况和当前的线程状态,动态地修改当前线程自旋的次数。
-
当另一个线程释放锁之后,当前线程要能够马上获得锁,所以如果有超过两个的线程同时访问这段代码,就算另外一个线程释放锁之后,当前线程也可能获取不到锁,还是要继续等待,空耗CPU。
从以上两点可以看出,当线程通过自旋获取不到锁了,比如临界区的操作太花时间了,或者有超过2个以上的线程在竞争锁了,轻量级锁的前提又不成立了。当虚拟机检查到这种情况时,又开始了膨胀的脚步。
互斥锁(重量级锁)
相比起轻量级锁,再膨胀的锁,一般称之为重量级锁,因为是依赖于每个对象内部都有的monitor锁来实现的,而monitor又依赖于操作系统的MutexLock(互斥锁)来实现,所以一般重量级锁也叫互斥锁。
由于需要在操作系统的内核态和用户态之间切换的,需要将线程阻塞挂起,切换线程的上下文,再恢复等操作,所以当synchronized升级成互斥锁,依赖monitor的时候,开销就比较大了,而这也是之前为什么说synchronized是一个很重的操作的原因了。 当然,升级成互斥锁之后,锁对象头的Markword内容也是会变化的,其内容如下:
每次检查当前线程是否获得锁,其实就是检查Mutex的值是否为0,不为0,说明其为其线程所占有,此时操作系统就会介入,将线程阻塞,挂起,释放CPU时间,等待下一次的线程调度。
好了,到这里,对于synchronized所修改的同步方法或者同步代码块,虚拟机是如何操作的,大家应该也有一个简单的印象了。 当使用synchronized关键字的时候,在java1.6之后,根据不同的条件和场景,虚拟机是一步一步地将偏向锁升级成轻量级锁,再最终升级成重量级锁的,而这个过程是不可逆的,因为一旦升级成重量级锁,则说明偏向锁和轻量级锁是不适用于当前的应用场景的,那再降级回去也没什么意义。
从这一点,也可以看出,如果我们的应用场景本身就不适用于偏向锁和轻量级锁,那么我们在程序一开始,就应该禁用掉偏向锁和轻量级锁,直接使用重量级锁,省去无谓的开销。
总结
在这里总结一下,在使用synchronized关键字的时候,本质上是否获得锁,是通过修改锁对象头中的markword的内容来标记是否获得锁,并由虚拟机来根据具体的应用场景来锁进行升级。
简单地将上述几个零散的markword变化合在一起,展示在下面:
最后
【程序视点】助力打工人减负,从来不是说说而已!
后续小二哥会继续详细分享更多实用的工具和功能。持续关注,这样就不会错过之后的精彩内容啦!
如果这篇文章对你有帮助的话,别忘了【点赞】【分享】支持下哦~
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
2023-12-19 Parallax.js:实现自适应智能设备方向的视差效果