前言
本期讲解 synchronized 工作的原理以及常见的锁优化机制,相信大家在看完这篇博文后对 synchronized 工作流程有一定的理解。话不多说,让我们快速进入学习吧~
目录
1. 锁的工作流程
2. 偏向锁
3. 轻量级锁和重量级锁
3.1 轻量级锁
3.2 重量级锁
4. 常见的锁优化
4.1 锁消除
4.2 锁粗化
1. 锁的工作流程
众所周知,synchronized 关键字是用来加锁的,加锁的原因就是多个线程抢占资源导致线程执行的过程不具备原子性。
JVM 将 synchronized 加锁的过程分为四个状态分为无锁、偏向锁、轻量级锁、重量级锁。刚开始是无锁状态,加上锁后处于偏向锁状态,锁有了竞争锁升级为轻量级锁,锁的竞争更加激烈了升级为重量级锁状态。
举例说明:
有个女孩叫如花,有个男孩叫阿三,如花是个漂亮的女孩,阿三是个帅气又有钱的男孩。
阿三追求如花,如花不想确定情侣关系也不想放弃阿三,于是如花吊着阿三,此时就处于一个偏向锁状态。阿三就像锁,锁的竞争不激烈,如花有恃无恐,锁处于 偏向锁 状态。
某一天,如花发现阿三正在被其他女孩追求,如花慌了立马跟张三确定男女关系,此时就处于轻量级锁状态。锁的竞争激烈了,锁升级为 轻量级锁。
随着日子一天天过去,越来越多女孩开始爱慕阿三。如花更加紧张了,生怕张三离开她,于是越来越限制张三的自由了,此时就处于重量级锁状态。锁的竞争更加激烈了,锁升级为 轻量级锁。
在上述例子中,阿三为锁,如花为 JVM。JVM 通过锁的竞争程度,来决定锁的策略是什么。
2. 偏向锁
第一尝试获取锁的线程,优先处于偏向锁的状态。此时这个线程并未进行加锁操作,而是通过一个 标记 来确定这是第一次获取锁的线程。
如果代码在整个运行过程中,没有遇到其他线程来竞争锁。就不会进行加锁了。但,如果有线程进行竞争锁,此时偏向锁就会变成一把真正的锁(轻量级锁)。
偏向锁,主要体现能不加锁就不加锁这个概念,它实际上并没有加锁只是通过一个标记来确定锁于无竞争状态,等到锁有竞争情况发生了,偏向锁才会变成一把锁(轻量级锁)。
举例:
21世纪流行着这样一段关系:
有一个男孩小强和一个女孩小美,小强是一把锁,小美是一个线程。小强和小美想过着夫妻一般的生活但不去领证。此时,小美在使用这把锁的时候锁的状态为偏向锁,因为没有其他线程竞争这把锁。
某一天,小强在公司认识了一个女孩小宣,小宣是一个线程。小宣爱慕小强,于是追求他。小美不想小强被抢走,于是和小强结婚(领证)。此时,小美使用小强这把锁就变成了轻量级锁状态。最后小强只得对小宣说:“你是个好女孩,我配不上你”。
3. 轻量级锁和重量级锁
轻量级锁为了避免进入同步状态,采用了一些基于CAS操作的优化手段,但如果线程CAS操作失败并达到一定次数后,会进入自旋锁状态等待获取锁。但如果自旋等待的时间过长,就需要升级为重量级锁,这是为了让其它线程有机会抢到锁。
升级为重量级锁后,会将当前线程阻塞并进入内核态,而不再直接进入自旋锁状态等待,这样可以避免浪费CPU资源。同时,重量级锁在释放锁的时候,也需要进行内核态的操作,因此其释放锁的代价相对较高。
关于CAS相关信息在这篇博文中有详细介绍:【Java多线程进阶】CAS机制_一只爱打拳的程序猿的博客-CSDN博客
3.1 轻量级锁
通过上文讲解,我们了解到随着其他线程竞争锁,锁由偏向锁状态升级为轻量级锁状态。轻量级锁状态,实际上就是自适应的自旋锁状态。
此处的轻量级锁,是通过 CAS 机制来实现的:
- 通过 CAS 机制检查并且更新一块内存。
- 如果更新成功,则加锁成功。
- 更新失败,则认为锁被占用,继续自旋式的等待。
自旋式,就是通过一个循环来判定锁是否被占用。如果锁被占用此时会无限循环,直到锁不被占用退出循环。在此期间,自选锁一直占用着 CPU 资源,比较浪费。
3.2 重量级锁
如果锁竞争愈发激烈,自旋锁不能快速获取到锁的状态,就会升级为重量级锁。此时的重量级锁会引申到内核态与用户态。
轻量级锁与重量级锁在这篇文章中有详细介绍:【Java多线程进阶】常见的锁策略_一只爱打拳的程序猿的博客-CSDN博客
4. 常见的锁优化
常见的锁优化有两种:锁消除于锁粗化。请看下方讲解。
4.1 锁消除
锁消除是一种编译器优化机制,当代码块被 synchronized 锁住时。如果该代码块只被某一线程独占则 synchronized 就被编译器消除了,因为编译器会检查并认为该代码块并不需要加锁,也就是我们所说的锁被消除了。
当然,多个线程使用多把锁时,只要互不干扰各自使用各自的锁资源,这种情况锁也会被消除。
锁消除是一种优化技术,它可以减少不必要的锁操作对性能的影响,提高程序的执行效率,节省系统消耗的资源。
4.2 锁粗化
谈到锁粗化,我们会联想到一个“粒度感”,代码越多粒度越粗,代码越少粒度越细。在写代码的过程中,我们很难兼容到要保证代码少又要保证代码好。
比如在某个场景下,我们频繁的使用 加锁/解锁 操作,编译器会通过优化手段将这些频繁的 加锁解锁 进行粗化,这样就能大大减小系统开销,也就是我们所说的锁粗化。
举例说明:
在公司上班,老板上午安排了一个任务给我,下午安排了个任务给我,晚上又安排了一个任务给我,要求我明天晚上之前要完成。
如果我今天下午完成第一个任务打电话给老板并汇报工作,明天上午完成了第二个任务再打电话给老板,明天晚上完成了第三个任务又打电话给老板。
老板心里肯定想,这人烦不烦一起给我不就得了。(当然,在任务不紧急的情况下)
如果我一次性汇报三个任务,此时就大大减少了老板接电话(上锁)挂电话(解锁)的开销。
synchronized工作原理是什么?
synchronized 刚开始是无锁的状态,当 synchronized 所修饰的资源被线程独占后就升级为偏向锁状态,当 synchronized 被多个线程竞争后就升级为轻量级锁,锁的竞争越来越激烈并且锁释放得很慢此时就会升级为重量级锁状态。
什么是偏向锁、轻量级锁、重量级锁?
偏向锁:第一个使用到锁的线程,此时并未进行锁操作,编译器会使用一个标识来确定这个锁只被一个线程使用。
轻量级锁:当锁被多个线程竞争了,此时锁会从偏向锁升级为轻量级锁,轻量级锁会通过 CAS 机制即自旋操作,来无限循环尝试获取锁直到获取到锁为止。因此,在获取到锁之前会一直占用CPU资源,容易浪费资源。
重量级锁:当锁竞争激烈并且锁资源释放过慢,此时锁会中轻量级锁升级为重量级锁,重量级锁会跟随系统的调度不再进行自旋等待,直到锁释放了,再参与锁的竞争,大大减少了系统资源的浪费。
🧑💻作者:一只爱打拳的程序猿,Java领域新星创作者,CSDN、阿里云社区优质创作者。
📒博客主页:这是博主的主页
🗃️文章收录于:Java多线程编程
🗂️JavaSE的学习:JavaSE
🗂️Java数据结构:数据结构与算法
本篇博文到这里就结束了,感谢点赞,评论,收藏,关注~