蜀国有一个皇帝叫蜀道难,他比较难伺候,别的皇帝早朝都是在大殿上同时接见所有大臣,共商国是。他不一样,他说早朝你们不要有事没事都跑过来叽叽喳喳,有事则来,无事则该干啥干啥去,然后安排太监每天早上在大门口守着,每次只允许一个大臣进来汇报情况。
“你敢多放进来一个就砍脑袋的干活。”
太监赶紧下跪,说“谪!”。
第一天,太监传话钦天监求见,皇帝允了,钦天监上殿报曰:“臣禀报,昨日我司夜观星象,西方忽现王星忽明忽暗,恐戎狄那边有乱。”
“朕知道了,退下吧”。一日无事。
第二天,太监传话钦天监求见,皇帝允了。一日无事。
第三天,太监传话钦天监求见……一日无事。
第四天,钦天监……一日无事。
第五天,皇帝不耐烦了,和贾太监说,钦天监这老家伙整天是不是闲着没事,以后他来了不用给我禀报,直接放他上殿讲,讲完让他走吧。
国泰民安的日子依旧过着,每天只有钦天监一个人来报告,贾太监每次看到是钦天监来了,也懒得搭理了,直接放他进去了。(这就是偏向锁,稍后我细细道来)
又一日,钦天监如往常进殿报道,贾太监站在门口打着盹,忽然耳边传来一个声音:
“贾太监,帮我禀告圣上,工部李尚书求见。”
“emmm…进去吧…嗯?等等,尚书大人你先等等,钦天监在里面,你等会再来求见吧。”太监一阵后怕,寻思着钦天监还在里面呢,这要是放进去了,我这脑袋可就没了,果然嗜睡误事。
过了一会儿,李尚书回来询问求见,被告知钦天监还没走,只好又离去。
又过了一会儿,李尚书又回来询问求见,正巧钦天监走了,太监进殿传话说工部李尚书求见,皇帝宣觐见,李尚书进殿上报了一番东南连连大雨,已派人去监察水利,修缮河堤。(这就是轻量级锁)
忽一日,西戎狄和北匈奴同时对帝国西方和北方发难,前线战事消息如片片雪花纷纷涌入京城,瞬间殿外来了一群大臣有要事禀告。
一会儿这个来问贾公公我可以进去了吗?一会儿那个来问贾公公我可以进去了吗?
把贾太监累的哟,一天下来光说“稍后再来”都把嘴皮子磨破了,没几日,贾太监就跪在皇帝面前哭泣道:“圣上啊,快想想办法呀,奴才这身子骨就要交代在门口了。”
皇帝一听,说你傻啊,叫他们一个个在门外排队啊,谁叫你要他们稍后来求见的。
贾太监细思大喜,觉得有理,次日在门口竖起一个牌子“禀报要事者,这边排队”,贾太监再也不用一个人对着一群人反复回话,只需要每次出来一个,然后传话放进去一个,就可以了。(这就是重量级锁)
上面这个故事,分别讲述了synchronized内部四种级别的状态,分别是:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。

重量级锁状态

我们首先从重量级锁开始讲,重量级锁是通过互斥量(Mutex)来实现的,即一个线程进入了synchronized同步块,在未完成任务时,会阻塞后面的所有线程。
就像上面的故事所讲的,要禀告要事的大臣只能在大殿门口外一个接一个的阻塞排队。
之所以称它为重量级锁,是因为Java线程是映射到操作系统的原生线程上的,如果要阻塞或唤醒一个线程,都需要依靠操作系统从当前用户态转换到核心态中,这种状态转换需要耗费处理器很多时间,对于简单同步块,可能状态转换时间比用户代码执行时间还要长,导致实际业务处理所占比偏小,性能损失较大。
当然这个在虚拟机层面进行了一些比如自旋等待,锁粗化等等的优化,避免陷入频繁的切换状态。在这里我就不细讲了,有兴趣的可以关注我,我后续再和各位看官讲上一讲。

轻量级锁状态

轻量级锁是JDK6引入的,它的轻量是相较于通过系统互斥量实现的传统锁,轻量锁并不是用来取代重量级锁的,而是在没有大量线程竞争的情况下,减少系统互斥量的使用,降低性能的损耗。
轻量级锁是通过CAS(Compare And Swap)机制实现的,即如果锁被其他线程所占用,当前线程会通过自旋来获取锁,从而避免用户态与核心态的转换。
就像上面故事所说的,大殿中钦天监在汇报工作,工部尚书要求见,并不需要贾太监每次都进去问一下皇帝,惹得皇帝龙颜大怒,而是大臣自己隔一段时间便来询问贾太监能不能进去,不能就稍后再来问,直到可以进去为止。

偏向锁状态

偏向锁也是JDK6引入的,它存在的依据是“大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得”。它是通过记录第一次进入同步块的线程id来实现的,如果下一个要进入同步块的线程与记录的线程id相同,则说明这个锁由此线程占有,可以直接进入到同步块,不用执行CAS。
就像故事中的,如果每天只有钦天监一个人来的话,就不用贾太监禀告了,贾太监每次一看到钦天监,寻思着,哟,钦天监呢,您自个儿直接进去吧,说完自个儿出来吧。
如果说轻量锁是为了消除系统互斥量带来的性能损耗,那么偏向锁就是为了消除CAS带来的性能损耗,使之在无竞争的情况下消除整个同步,性能无限接近非同步。

如何通过这四种状态实现性能大幅度提升的

Java对象头

要说这个问题,我们需要先讲一下Java对象头,每个对象都会有一个对象头,它分为三个部分:

从表格可见,synchronized锁的信息是存在对象头里一个叫Mark Word的区域里的,考虑到虚拟机的空间效率,Mark Word被设计成非固定的数据结构,会根据对象的状态复用存储空间来存储不同的内容:

锁的升级

当JVM启用了偏向锁模式(JDK6以上默认开启),新创建对象的Mark Word是未锁定,未偏向但可偏向状态,此时Mark Word中的Thread id为0,表示未偏向任何线程,也叫做匿名偏向(anonymously biased)。

偏向锁状态--->无锁不可偏向状态/轻量级锁状态

当第一个线程尝试进入同步块时,发现Mark Word中线程ID为0,则会使用CAS将自己的线程ID设置到Mark Word中,并且,在当前线程栈中由高到低顺序找到可用的Lock Record,将线程ID记录下。完成这些,此线程就获取了锁对象的偏向锁。
当该偏向线程再次进入同步块时,发现锁对象偏向的就是当前线程,会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来统计重入的次数,然后继续执行同步块代码,因为线程栈是私有的,不需要CAS指令进行操作,所以在偏向锁模式下,同一个线程,只会执行一个CAS,之后获取释放锁只需要对Lock Record做操作,性能损耗基本可以忽略。
当另外一个线程试图进入同步块时,发现Mark Word中线程ID与自己不相符,这个时候就会引发偏向锁的撤销,变成无锁不可偏向状态或轻量级锁状态,当然,这只是宏观上的描述,严格意义上讲是不准确的,因为里面还存在重偏向机制,这里就不过于深入,在后续的文章中,我会专门出一篇文章,给各位看官详细介绍偏向锁到底是怎么回事。

无锁不可偏向状态--->轻量级锁状态

当锁对象变成无锁不可偏向状态时,多个线程运行到同步块以后,会检查锁对象状态值标志是否加锁,如果没有锁,就把锁对象的Mark Word信息拷贝存储到当前线程栈桢中Lock Record里,然后通过CAS尝试把对象的Mark Word的值改变成一个指向自己线程的指针。如果成功,则当前线程获得锁对象的轻量级锁,其他线程的CAS就会失败,因为锁对象的Mark Word已经变成一个新的指针了,必须等待线程释放锁,此时其他线程则通过自旋来竞争锁。当获取锁的线程执行完毕释放锁的时候,会将Lock Record里面之前拷贝的值还原到锁对象的Mark Word中。

轻量级锁状态--->重量级锁状态

当自旋次数超过JVM预期上限,会影响性能,所以竞争的线程就会把锁对象的Mark Word指向重锁,所谓的重锁,实际上就是一个堆上的monitor对象,即,重量级锁的状态下,对象的Mark Word为指向一个堆中monitor对象的指针。
然后所有的竞争线程放弃自旋,逐个插入到monitor对象里的一个队列尾部,进入阻塞状态。
当成功获取轻量级锁的线程执行完毕,尝试通过CAS释放锁时,因为Mark Word已经指向重锁,导致轻量级锁释放失败,这时线程就会知道锁已经升级为重量级锁, 它不仅要释放当前锁,还要唤醒其他阻塞的线程来重新竞争锁。
大概流程如下图所示:

这里有一点需注意的是:锁只能升级,不能降级。

锁的对比

优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
轻量级锁 竞争的线程不会堵塞,提高了程序的响音速度 始终得不到锁的线程,使用自旋会消耗CPU 追求响应时间,同步块执行速度非常快
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行速度较慢

synchronized的底层实现

synchronized无非以下两种:
1.对象锁:修饰非静态方法,修饰代码块
2.类锁:修饰静态方法,修饰代码块
其中按照修饰类型来分,又可以分为代码块同步和方法同步

代码块同步

代码块同步锁的是对象,使用monitorenter和monitorexit指令实现的。虽然我知道多一行代码少一位看官的定理,但是这里还是必须贴一张代码图,来证明我没有瞎说,是有理有据的“理据服”。
想要降服妖怪,就得先将其打回原形,所以我们先对一段简单的代码进行反编译,得到它的字节码。

  final Object lock = new Object();public int subtr(int i){synchronized (lock){return i-1;}}

字节码:

对象锁反编译

可以看出,monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit插入到同步代码块结束的地方,正常情况下monitorenter和monitorexit是一对一的匹配,而后面又出现了一个monitorexit,是因为那里是异常处,用来保证方法执行异常的时候,可以自动释放锁,而不会造成死锁。

方法同步

方法同步的实现官方没有透露,我们尝试对一个方法同步的代码进行反编译。

    public synchronized int add(int i){return i+1;}

字节码:

同步方法反编译

从字节码里也看不到monitorenter和monitorexit,只能发现flags那里,多了一个ACC_SYNCHRONIZED的标示,没什么头绪。不过我猜想,底层应该是锁方法所属的对象或类。

这就是synchronized的大致原理,打回原形之后来看,是不是就觉得也不过如此?

锁原理之synchronized相关推荐

  1. 【重难点】【JUC 04】synchronized 原理、ReentrantLock 原理、synchronized 和 Lock 的对比、CAS 无锁原理

    [重难点][JUC 04]synchronized 原理.ReentrantLock 原理.synchronized 和 Lock 的对比.CAS 无锁原理 文章目录 [重难点][JUC 04]syn ...

  2. Java Synchronized 重量级锁原理深入剖析上(互斥篇)

    前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...

  3. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  4. 分布式锁原理——redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  5. 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...

  6. java synchronized 原理_Java Synchronized的原理

    我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的: public classSynchronizedDemo{public voidmethod(){synchro ...

  7. ReenTrantLock可重入锁(和synchronized的区别)总结

    ReenTrantLock可重入锁(和synchronized的区别)总结 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也 ...

  8. Java之多线程里面的锁理解以及synchronized与Lock的区别

    一.宏观的说下锁的分类 1)锁分为乐观锁.悲观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不 ...

  9. 最通俗易懂的乐观锁与悲观锁原理及实现

    转载自  最通俗易懂的乐观锁与悲观锁原理及实现 一.乐观锁 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据 ...

最新文章

  1. 位运算一些简单的应用
  2. 【Win 10 应用开发】获取本机的IP地址
  3. pcm 采样率转换_Cool Audio Video Converter(音视频格式转换工具)V2.18 最新版 - 绿色先锋下载 -...
  4. 宏BOOST_TEST_ALL_WITH的用法实例
  5. 維護webflow:request、session、application、page
  6. c++ template(4)基本技巧
  7. 洛谷P2014【树形dp】
  8. 凭什么说“Python 太慢,Java 太笨拙,我讨厌 JavaScript”?
  9. ubuntu14.04无法打开新加卷
  10. mysql ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing t
  11. 利用swap()函数来收缩内存
  12. 网上测试性格的软件依据是什么意思,(观点)mbti性格测试如此流行,它有科学依据吗?...
  13. 机器学习之密度聚类(DBSCAN)
  14. android实现按键找图功能,从零学起之安卓篇《按键精灵安卓版找图找色应用汇总介绍》更新20140603 _ 教程中心 - 按键精灵论坛...
  15. Excel表头怎么设计?这里有超全面的表头设计方法!一分钟可学会
  16. 小故事分解区块链--科普篇
  17. python爬取淘宝数据遇见的坑
  18. linux OS与SQL修改时区,系统时间
  19. 如何防止亚马逊账号被关联?
  20. 操盘建议----全球顶尖交易员的成功实践和心路历程(三)

热门文章

  1. sm2 java_关于bouncycastle下国密SM2 API的使用
  2. NBA球队名称英文简写_henry_dx_新浪博客
  3. 2022-2028全球与中国移动集装箱租赁市场现状及未来发展趋势
  4. lisp语言画阿基米德线_如何用VB语言Line方法绘制阿基米德螺线
  5. 建筑排风机、送风机、补风机、加压(正压)送风机、排烟机的作用
  6. CAS:32171-39-4,mPEG-Acrylate,mPEG-AC,甲氧基-聚乙二醇-丙烯酸酯
  7. linux默认编译多核,[转载]【原创】Linux下安装单机多核并行lammps(附meam包)
  8. 408计算机考研--数据结构--2020年统考真题(C语言)
  9. ggplot2实现色阶散点图+曲线拟合图
  10. java map 多个key_java ListMap使用多个或者任意个数的key进行排序