Synchronized 都用过,那么它是怎么做到简单修饰一下就能做到并发场景下资源同步的呢?让我们来揭开它神秘的面纱~

JDK6以前Synchronized 只有重量级锁,很笨重,就是当一个线程t1访问同步资源时,立马上锁,其它线程过来竞争锁的话直接阻塞,然后等待t1释放锁,线程唤醒再去加重量级锁,
线程的阻塞和唤醒需要OS切换CPU(内核态与用户态)的状态来完成,这个状态转换消耗处理器时间,开销大,影响性能,
so~ 在JDK 6 引入了偏向锁(默认开启偏向锁) 和 轻量级锁

所以现在有四种锁状态,无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 只能升级,不能降级

synchronized 的锁是加在对象头的,
我们都知道对象是在堆内存分配空间的,,在JVM中,对象在内存中的布局又分为:对象头、实例数据、对齐填充,盗图如下,


我们专门来看看 对象头

对象头

盗图如下:
普通对象的 Header = Mark Word (标记字段)+ Klass Word(类型指针)

数组对象的 Header = Mark Word (标记字段)+ Klass Word(类型指针) + array length(数组长度)

![在这里插入图片描述](https://img-blog.csdnimg.cn/e7b88385f54a43cda5cda1ed632d1f12.png

所以**对象在堆内存的存储结构**应该如下:


Klass Pointer (类型指针):简单说一下,就是指向类元数据的指针,JVM通过这个指针确定当前对象是属于哪个类的实例
Mark Word(标记字段):重点说这个标记字段,它是和对象本身数据没有关系的,用来存储对象运行时数据,存储数据和结构是变化的
结构如下盗图:



可以看到,针对不同锁的状态,存储的数据不同,翻译过来就是盗图如下(32bit):

下面我们逐个状态来康康:

NORMAL - 无锁状态


Mark Word - 标记字段 里边存了
Hash Code 对象标识Hash码
age: GC年龄,年轻代中s1、s2复制一次,年龄+1,阈值15,固定4bit,4位也就意味着最大1111 = 15
biased_lock 是否是偏向锁,0-否 1-是,JDK6以上默认开启偏向锁,可以通过参数来禁用偏向锁

 -XX: -UseBiasedLocking  //禁用-XX: +UseBiasedLocking  //启用

lock: 锁标记,盗图如下
无锁和偏向锁需要 biased_lock 和 lock组合来判断

Biased - 偏向锁状态

首先来说下什么是偏向锁,顾名思义,它很偏向,锁老给一个线程获取,嘎嘎~
引入偏向锁是为了在没有多线程竞争资源时减少CAS(硬件实现原子性)次数从而提高性能,不细说了

thread: 持有偏向锁的线程id
epoch: 线程获取偏向锁时的时间戳
age: GC年龄
Biased_lock: 是否是偏向锁, 1-是
lock: 锁标记 01 同无锁

偏向锁获取

  • 当一个线程访问同步变量(Synchronized修饰),先去对象header中判断biased_lock = 1 ,是否为偏向锁
  • 然后判断thread = 当前线程id,判断是否偏向当前线程
  • 如果偏向当前线程,说明当前线程持有锁,直接执行同步代码
  • 如果同步对象的header中mark word 中thread 与当前线程不等,则通过CAS操作竞争锁,将当前线程id赋值给thread
  • 如果竞争成功则持有锁,执行同步代码,如果竞争失败,则说明锁被其它线程占有,偏向锁同一时刻有竞争就会在safepoint(全局安全点)挂起线程,升级偏向锁为轻量级锁,然后挂起线程继续执行同步代码,(我觉这点很好理解,偏向锁的出现本来就是为在无多线程竞争的情况下放置锁获取和释放依赖多次CAS导致系统开销大问题,现在有并发竞争了你还偏向,就给一个线程执行,那其它线程猴年能获取到锁啊)
  • 持有偏向锁的线程不会主动释放锁

panda白话总结
线程访问同步对象,先康康对象头里说它是偏向锁了吗,是的话康康偏向的是我吗,是我就放肆了,执行代码,不是我的话那我试试(CAS)我能加锁不,加锁成功,我又放肆了,后面这个对象偏向我了,我再来请求就不用做CAS这样无用功了,加锁不成功的话,害~别人来早偏向他了,那不行啊,我也得用呢,自旋等会吧,对象一看,哦?同时又来一个,那好吧,我火了,不能偏向了,找个安全时间点(safepoint)升级为轻量级锁你们去竞争吧,嘎嘎

LightWeighed Locked - 轻量级锁


ptr_to_lock_record : 指向栈帧中Lock Record的指针
lock: 锁标记 00

上面说了偏向锁,和它相同锁标志的还有无锁呢,无锁就是没有现成上锁呗,当有现成访问时也会直接升级为轻量级锁
轻量级锁是什么呢??
是针对重量级锁来的,重量级锁竞争现成直接阻塞,锁释放了再去环境阻塞现成,系统进行现成阻塞和唤醒,恢复现场,开销比较大,所以出了轻量级锁,轻量级锁竞争现成不直接阻塞,而是自旋等待(等一会),是通过自旋锁机制实现的,自选到一定次数再阻塞,这是轻量级锁也升级为重量级锁,如果自旋过程中获取到锁,那就很美滋滋啦。

无锁 -> 轻量级锁获取

1、首先一个对象创建老实巴交地呆在堆内存中, 此时对象头是无锁状态,如下:


2、此时有个线程请求过来要上=加锁,我们都知道每个线程分配一个栈空间,那这个线程能否加锁成功,要做几件事

  • 栈帧(每个方法创建一个栈帧压栈)中创建一个Lock Record(锁记录)空间,锁记录中有俩属性_displaced_headerowner
  • 拷贝一份对象头中Mark Word 存放到_displaced_header
  • 此时biased_locked -偏向锁标志值为 0 ,即不是偏向锁,所以它要上一把轻量级锁,上面说了轻量级锁mark word中除了锁标记lock,还存了**ptr_to_lock_record :** 指向栈帧中Lock Record的指针,所以第三件事做的就是:JVM通过CAS操作将对象中的mark word 存储的ptr_to_lock_record更新为指向栈帧中Lock Record 的指针
  • 将栈帧里Lock Record 中的owner 指针指向对象,代表这个对象被我占了,i am the owner,嘎嘎
  • 修改堆中对象头的Mark Word 中lock 标志为00,告诉其他线程这个对象被轻量级锁锁了,其他线程竞争锁时可以自旋等待,不用立即释放CPU时间片进入阻塞,不赘述了。。
  • 上锁成功
    结果如下盗图:

panda白话总结
针对以对象来说,我只要知道两件事,1、lock=00,告诉竞争线程我被轻量级线程锁了,你们该自旋自旋
2、ptr_to_lock_record = 持有锁线程LockRecord指针,我被谁给锁了
针对于持有锁线程来说,我也只要知道两件事1、对象mark word副本(duck不必)2、owner = 对象指针 知道我锁的是哪个对象

HeavyWeight Locked - 重量级锁

ptr_to_heavyweight_monitor: 指向monitor对象的起始地址,monitor对象稍后说
lock: 锁标记 11

重量级锁一直在说,不赘述了。。

轻量级锁 -> 重量级锁获取

上面说过,重量级锁对象头mark word中除了lock锁标记(10)还存储了**ptr_to_heavyweight_monitor:** 指向monitor对象的起始地址,,看来重量级锁是Monitor对象来实现的啊,
那我们就来康康什么是Monitor

Monitor - 监控器 or 管程

每个java对象都有一把看不见的锁,成为内部锁或者Monitor锁,同对象生命周期一致
来看它的数据结构:

ObjectMonitor() {_header       = NULL;_count        = 0; //记录个数_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;}

我觉有用的就是:
_WaitSet : 等待wait线程集合
_EntryList :等待block线程集合
owner : 哪个线程正在持有锁
加锁步骤

  • 当一个线程尝试获取重量级锁,把同步对象与OS提供的Monitor关联(只有访问synchrized同步对象加锁时才关联),即上面说的ptr_to_heavyweight_monitor记录Monitor的起始地址
  • 将Monitor中owner指向当前线程
  • 即当前线程获取锁成功,其它线程请求锁进入阻塞队列EntryList,等待锁释放被唤醒进行非公平竞争锁
  • 持有锁线程调用wait()中断进入waitSet
    如下图


panda白话总结:
针对对象,1、lock=10,重量级锁 2、ptr_to_heavyweight_monitor = monitor地址,我关联的monitor是谁,有事请找它
针对monitor,1、owner = 持有锁线程id,我管的这个对象被哪个线程占了,2、其它这些请求的线程该进哪个队列进哪个队列waitset 、entryset

锁状态转换

这个大哥图画的不错,简单明了

panda总结:
创建一个对象在堆内存分配一块空间,此时对象锁状态可能为无锁 or 偏向锁
当对象为无锁状态被synchronized修饰时,代表并发情况下只能同步访问,
此时一个线程请求过来,直接升级为轻量级锁,上锁成功,其它线程请求自旋等待,自旋超过一定次数进入阻塞队列,此时轻量级锁升级为重量级锁,此时交给os创建的monitor来处理锁的获取和释放

当对象为偏向锁状态被synchronized修饰时,代表并发情况下只能同步访问,
此时一个线程请求过来,偏向锁将偏向这个线程,下次访问直接执行代码即可,此时有其它线程同步请求,则释放偏向,找个安全点将持有线程挂起,此时如果线程存活则直接获升级后的轻量级锁,如果线程不存活则变为无锁状态。

就到这吧~唉呀妈呀 脑瓜疼~

Panda白话 - Synchronized底层同步机制相关推荐

  1. java syncr_JAVA基础—Synchronized线程同步机制

    Synchronize的使用场景 Synchronize可以使用在一下三种场景,对应不同的锁对象 场景 synchronized代码块 synchronized方法 synchronized静态方法 ...

  2. java底层机制_Java同步机制的底层实现

    在多线程编程中我们会遇到很多需要使用线程同步机制去解决的并发问题,而这些同步机制就是多线程编程中影响正确性和运行效率的重中之重.这不禁让我感到好奇,这些同步机制是如何实现的呢?好奇心是进步的源泉,就让 ...

  3. synchronized同步机制——如果你愿意一层一层剥开我的心

    前言 synchronized,是解决并发情况下数据同步访问问题的一把利刃.那么synchronized的底层原理是什么呢?下面我们来一层一层剥开它的心,就像剥洋葱一样,看个究竟. Synchroni ...

  4. 学习笔记day12 synchronized底层实现及锁升级机制

    原博客:https://blog.csdn.net/weixin_40394952/article/details/118693945 一.synchronized使用方法 1.修饰实例方法,对当前实 ...

  5. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

  6. Java线程同步机制synchronized关键字的理解

    由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 需要明确的几个问题: ...

  7. 多线程高并发 底层锁机制与优化的最佳实践——各种锁的分类 || synchronized 关键字 倒底锁的是什么东西?|| CAS与ABA问题||锁优化||轻量级锁一定比重量级锁的性能高吗

    多线程高并发 底层锁机制与优化的最佳实践 各种锁的分类 加上synchronized 关键字,共享资源就不会出错 synchronized 关键字 倒底锁的是什么东西? synchronized 锁的 ...

  8. Java的synchronized关键字:同步机制总结

    不久前用到了同步,现在回过头来对JAVA中的同步做个总结,以对前段时间工作的总结和自我技术的条理话.JAVA的synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同 ...

  9. Java多线程的同步机制:synchronized

    如果程序是单线程的,就不必担心此线程在执行时被其他线程"打扰",就像在现实世界中,在一段时间内如果只能完成一件事情,不用担心做这件事情被其他事情打扰.但是,如果程序中同时使用多线程 ...

最新文章

  1. Linux Supervisor的安装与使用入门
  2. Python 解决 :NameError: name ‘reload‘ is not defined 问题
  3. OSChina 周三乱弹 —— 孤独到都和病毒发生了感情了
  4. linux查看某个端口是被哪个进程占用的
  5. LeetCode 530. 二叉搜索树的最小绝对差 思考分析
  6. datetime报错 sql脚本_《SQL必知必会》附录A样例表的获取和导入
  7. 华为Mate 40 Pro最新渲染图曝光:后置相机模组有变化
  8. bzoj 1015 [JSOI2008]星球大战starwar
  9. jenkins(六):Jenkins节点管理
  10. 手机modem开发之VoLTE信令
  11. 计算机硬件acc作用,累加器A的主要作用是什么_一文解析累加器a和acc的区别
  12. MySQL计算同比和环比
  13. 瑞幸最新股权曝光:大钲资本持股33.9% 有57%投票权
  14. MIDI通信协议-数据字节:找到中央C(音名:C4)
  15. Tomcat11——Tomat集群
  16. PCB图纸太小元件放不下怎么办
  17. 【狂神说】 mysql 自学总结 7~9章
  18. 如何继承字走三国武器
  19. LSM-tree基本原理及应用
  20. Transformer在3D点云中的应用综述(检测/跟踪/分割/降噪/补全)

热门文章

  1. 7大变化 看微软IE浏览器创新之处
  2. 2015年蓝桥杯省赛C++(B组) 第三题 三羊献瑞
  3. 测试工具charles(花瓶)安装 以及简单的使用(2)
  4. Excel辅助列的使用方法案例
  5. 第60件事 关于产品运营的10个故事
  6. python绘制有箭头的直线_Python绘图库Matplotlib,如何绘制箭头?
  7. PageHelper在对mybatis一对多分页不正确的问题
  8. VC读取PE文件的OEP(程序入口)
  9. 基于IP播放TS流的码率控制策略
  10. 微信小程序获取openid、sessionKey