深入详解Synchronized同步锁的底层实现

前言

谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如:

  1.  synchronized的底层实现原理
  2. synchronized锁于JVM的实现
  3. synchronized锁升级顺序
  4.  synchronized锁的优劣势与应用场景

Synchronized

synchronized 翻译为中文的意思是同步,也称之为”同步锁“。

synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。

Synchronized的使用

1.synchronized的3种使用方式

  •  修饰实例方法:作用于当前实例加锁
  •  修饰静态方法:作用于当前类对象加锁
  •  修饰代码块:指定加锁对象,对给定对象加锁

2.synchronized的代码范例

Synchronized的底层实现
synchronized的底层实现是完全依赖与JVM虚拟机的。

所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。

1.Java对象头

在JVM虚拟机中,对象在内存中的存储布局,可以分为三个区域:

  •  对象头(Header)
  •  实例数据(Instance Data)
  •  对齐填充(Padding)

Java对象头主要包括两部分数据:

  •  类型指针(Klass Pointer):是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
  •  标记字段(Mark Word):用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.

2.Java锁对象存储位置

所以,很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。

3.Monitor

synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现)的起始地址。每个对象实里都会有一个 monitor。

Monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间中有一些被保护的数据,Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。

使用syncrhoized加锁的同步代码块在字节码引擎中执行时,主要就是通过锁对象的monitor的取用与释放来实现的。

4.线程状态流转在Monitor上体现

描述为对象监视器,当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:

  •  Contention List:所有请求锁的线程将被首先放置到该竞争队列
  •  Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
  •  Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
  •  OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
  •  Owner:获得锁的线程称为Owner
  •  !Owner:释放锁的线程

下图反映了个状态转换关系

Synchronized 锁的升级顺序

锁解决了数据的安全性,但是同样带来了性能的下降。hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。所以基于这样一个概率。

synchronized 在JDK1.6 之后做了一些优化,为了减少获得锁和释放锁来的性能开销,引入了偏向锁、轻量级锁、自旋锁、重量级锁,锁的状态根据竞争激烈的程度从低到高不断升级。

1.偏向锁

偏向锁是JDK6中引入的一项锁优化,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。

2.轻量级锁

如果明显存在其它线程申请锁,那么偏向锁将很快升级为轻量级锁。

3.自旋锁

自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

4.重量级锁

指的是原始的Synchronized的实现,重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。

偏向锁、轻量级锁、重量级锁优劣势比较

 

 

全部评论

相关推荐

面试官全程关摄像头1.自我介绍一下2.React和Vue哪个更熟悉一点3.你在之前那段实习经历中有没有什么技术性的突破(我只是实习了44天工作28天,我把我能说的都说了)4.你封装的哪个表单组件支不支持动态传值5.自己在实习阶段Vue3项目封装过hook吗6.hook有什么作用7.Vue2和Vue3的响应式区别(我说一个是proxy是拦截所有的底层操作,Object.defineProperty本身就是一个底层操作,有些东西拦截不了,比如数组的一些操作还有等等,面试官就说实在要拦截能不能拦截????我心想肯定不行呀,他的底层机制就不允许吧)8.pinia和vuex的区别(这个回答不出来是我太久没用了)9.pinia和zustand的区别,怎么选(直接给我干懵了)(我说react能用pinia吗  他说要用的话也可以)10.渲染一万条数据,怎么解决页面卡顿问题(我说分页、监听滚轮动态加载,纯数据展示好像还可以用canvas画)(估计是没说虚拟表单,感觉不满意)11.type和interface的区别12.ts的泛型有哪些作用(我就说了一个结构相同但是类型不同的时候可以用,比如请求响应的接口,每次的data不同,这里能用一个泛型,他问我还有什么)13.你项目用的是React,如果让你再写一遍你会选择什么14.pnpm、npm、yarn的区别15.dependencies和devdependencies的区别总而言之太久没面试了,上一段实习的面试js问了很多。结果这次js一点没问,网络方面也没考,表现得很一般,但是知道自己的问题了  好好准备,等待明天的影石360和周四的腾讯了  加油!!!
解zj:大三的第一段面试居然是这样的结局
查看15道真题和解析
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务