且听我一个故事讲透一个锁原理之synchronized

微信公众号:IT一刻钟
大型现实非严肃主义现场
一刻钟与你分享优质技术架构与见闻,做一个有剧情的程序员
关注可第一时间了解更多精彩内容,定期有福利相送哟。

故事从这里展开

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

重量级锁状态

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

轻量级锁状态

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

偏向锁状态

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

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

Java对象头

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

内容 说明
Mark Word 存储对象的hashcode或锁信息
Class Metadata Address 存储到对象类型数据的指针
Array length 数组的长度(如果当前对象是数组)

从表格可见,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;
        }
    }

字节码:


对象锁反编译.png

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

方法同步

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

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

字节码:


同步方法反编译.png

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

这就是synchronized的大致原理,打回原形之后来看,是不是就觉得也不过如此?有什么疑问或更好的解读,可以在下方留言,我们进行愉快友好的磋商交流。
如果觉得有用,记得分享~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容