多线程知识梳理(3) - synchronized 三部曲之锁优化

一、前言

多线程知识梳理(2) - synchronized 基本使用 中,我们介绍了使用重量锁来实现的synchronized。今天,我们就来一起学习一下在JDK 1.6之后,对synchronized所采取的一系列优化措施。

二、对象头 & Monitor Record

在介绍优化方法之前,我们需要介绍两个重要的概念Java对象头和Monitor

2.1 对象头

Java&Android 基础知识梳理(3) - 内存区域 中介绍内存区域的时候,对于一个Java对象所占的内存区域是这么介绍的:


在运行过程中,对象头所包含数据的含义不是固定不变的,随着锁状态标志位(下图中红框的范围)的改变,其它字段所表示的含义也不同,以32位的虚拟机为例,下图就是锁状态标志位所对应的数据结构含义:

2.2 Monitor Record

Monitor是线程私有的数据结构,由于一个线程可能进入多个不同的同步方法,这些方法有可能会关联到不同的Monitor,因此每一个线程都有一个可用的Monitor列表,同时还有一个全局的可用列表,Monitor数据结构包括以下成员变量:

  • Owner:初始时为空表示当前没有任何线程拥有该Monitor,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为空。
  • EntryQ:关联一个系统互斥锁,阻塞所有试图获得Monitor但是最终失败了的线程。
  • RcThis:表示blockedwaiting在该Monitor上的所有线程的个数。
  • Nest:用来实现重入锁的计数。
  • HashCode:保存从对象头拷贝过来的HashCode值。
  • Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值:0表示没有需要唤醒的线程,1表示要唤醒一个继任线程来竞争锁。

三、实现优化

JDK 1.6之后,它对于锁进行了一系列的优化措施,主要包括:自适应自旋锁、锁消除和锁粗化。

3.1 自旋锁

由于线程的阻塞和唤醒需要CPU从用户态转换成核心态,而频繁的阻塞和唤醒对CPU来说是一件负担很重的工作。

因此,我们在发现锁已经被其它线程占有时,并不直接让当前线程进入阻塞状态,而是让线程执行一段无意义的循环,待循环结束后,如何仍然无法获取到锁,那么才进入阻塞状态。

决定自旋锁性能的关键在于自旋次数的选择,在JDK 1.6之后,引入了自适应自旋锁,它会根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定新的自旋次数。

3.2 锁消除

JVM检测到不可能存在共享数据竞争,会对同步锁进行锁消除。

3.3 锁粗化

在使用同步锁的时候,需要让同步块的作用范围尽可能地小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。

然而,如果一系列连续加锁解锁操作,可能会导致不必要的性能损耗,所以有时可以将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

四、状态优化

JDK 1.6之前,锁只有两种状态:无锁状态和重量级锁状态,而在这之后增加为四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,这种改进基于两点考虑:

  • 无锁状态和重量级锁状态之间的切换是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本很高。
  • 实验研究发现,对于绝大部分的锁,在整个生命周期内都是不存在竞争的。

需要注意,对于锁的这四种状态,它们会随着竞争的激烈而逐渐升级,但是它只允许锁升级,不允许锁降级。

无锁状态和重量级锁状态都比较好理解,下面我们主要介绍新增的两种锁状态:偏向锁状态轻量级锁状态

整个转换的流程图如下所示,在后面的介绍中可以参考:


4.1 偏向锁状态

引入偏向锁的目的是:在无多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径,它的理想情况下是在无竞争时把整个同步都去掉,连CAS操作都省略。

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

4.1.1 获取偏向锁

(a) 前提条件

获取偏向锁的前提条件是synchronized所修饰的对象处于可偏向状态

  • 锁状态为01
  • 偏向锁状态为1

(b) 获取过程

当满足前提条件时,再去判断对象的Mark Word中的线程ID是否指向当前线程

  • 如果不指向当前线程,那么通过CAS操作竞争锁
    • 竞争成功:将Mark Word的线程ID替换为当前线程ID,接着执行同步代码块
    • 竞争失败:证明存在多线程竞争的情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块
  • 如果指向当前线程,那么执行同步代码块

4.1.2 释放偏向锁

(a) 前提条件

释放偏向锁的前提条件是其它的线程在竞争偏向锁的过程中出现了失败的情况,并且偏向锁的释放需要等待到达全局安全点。

(b) 释放过程

当满足释放偏向锁的前提条件时,首先会暂停拥有偏向锁的线程,接着判断锁对象是否处于被锁定的状态,决定锁标志位下一步的状态:

  • 如果未被锁定,那么将锁标志至为01,偏向锁状态置为0,表示它处于无锁,且不可偏向状态。
  • 如果已经被锁定,那么将锁标志置为00,表示它处于被轻量级锁定的状态。

4.2 轻量级锁状态

引入轻量级锁的目的是:在无多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

4.2.1 获取轻量级锁

(a) 前提条件

获取轻量级锁的前提条件时当前对象处于无锁状态,

  • 锁状态标志位为01
  • 偏向锁标志位为0

(b) 获取过程

JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储对象目前的Mark Word的拷贝,之后JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针:

  • 操作成功:将锁标志置为00,表示处于锁定的状态,之后执行同步操作。
  • 操作失败:那么检查对象的Mark Word是否指向当前线程的栈针
  • 如果是,则直接执行同步代码块
  • 如果不是,说明该锁对象已经被其他线程抢占了,此时轻量级锁升级为重量锁,锁标志位变为10,后面等待的线程将会进入阻塞状态。

4.2.2 释放轻量级锁

(a) 释放过程

轻量级锁的释放也是通过CAS操作来进行的:

  • 取出在获取轻量级锁时,保存在Displaced Mark Word中的数据。
  • CAS操作将取出的数据替换到当前对象的Mark Word中:
  • 如果成功,则说明释放锁成功
  • 如果失败,说明有其它线程尝试获取该锁,那么需要在释放锁的同时,唤醒需要被唤醒的线程

对于轻量级锁,它性能提升的依据是默认"对于绝大部分的锁,在整个生命周期内是不会存在竞争的",如果不符合这种情况,那么除了互斥的开销外,还有额外的CAS操作,这样轻量级锁比重量级锁更慢。

五、参考文章

Java 并发编程:Synchronized 底层优化(偏向锁、轻量级锁)
死磕 Java 并发 -----深入分析 synchronized 的实现原理
深入理解 Java 锁与线程阻塞

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

推荐阅读更多精彩内容