首页 > 编程源码 > synchronized锁优化(下)

synchronized锁优化(下)

楼主:知安 [1级] · 2019-11-21 ·  浏览511 · 编程源码 · ID:

偏向锁、轻量级锁、重量级锁

从Java对象头的Mark word中可以看到,synchronized锁一共具有四种状态:无锁、偏向锁、轻量级锁、重量级锁。

偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

偏向锁

目的:大多数情况下锁不仅不存在多线程竞争,而且总是由同一个线程多次获取,所以引入偏向锁让线程获得锁的代价更低。

偏向锁认为环境中不存在竞争情况,锁只被一个线程持有,一旦有不同的线程获取或竞争锁对象,偏向锁就升级为轻量级锁。

偏向锁在无多线程竞争的情况下可以减少不必须要的轻量级锁执行路径。

轻量级锁

目的:在大多数情况下同步块并不会出现竞争情况,大部分情况是不同线程交替持有锁,所以引入轻量级锁可以减少重量级锁对线程的阻塞带来的开销。

轻量级锁认为环境中线程几乎没有对锁对象的竞争,即使有竞争也只需要稍微等待(自旋)下就可以获取锁,但是自旋次数有限制,如果超过该次数,则会升级为重量级锁。

重量级锁

监视器锁Monitor

7. 锁的膨胀过程

synchronized锁膨胀过程就是无锁 → 偏向锁 → 轻量级锁 → 重量级锁的一个过程。这个过程是随着多线程对锁的竞争越来越激烈,锁逐渐升级膨胀的过程。

如下分析,从一个没有线程访问的锁逐渐升级到重量级锁的过程:

1)一个锁对象刚刚开始创建的时候,没有任何线程来访问它,此时线程状态为无锁状态。Mark word(锁标志位-01 是否偏向-0)

2)当线程A来访问这个对象锁时,它会偏向这个线程A。线程A检查Mark word(锁标志位-01 是否偏向-0)为无锁状态。此时,有线程访问锁了,无锁升级为偏向锁,Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID)

3)当线程A执行完同步块时,不会主动释放偏向锁。持有偏向锁的线程执行完同步代码后不会主动释放偏向锁,而是等待其他线程来竞争才会释放锁。Mark word不变(锁标志位-01,是否偏向-1,线程ID-线程A的ID)

4)当线程A再次获取这个对象锁时,检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A,可以直接执行同步代码。这样偏向锁保证了总是同一个线程多次获取锁的情况下,每次只需要检查标志位就行,效率很高。

5)当线程A执行完同步块之后,线程B获取这个对象锁 检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A。有不同的线程获取锁对象,偏向锁升级为轻量级锁,并由线程B获取该锁。

6)当线程A正在执行同步块时,也就是正持有偏向锁时,线程B获取来这个对象锁。

检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A。

线程A撤销偏向锁:

等到全局安全点执行撤销偏向锁,暂停持有偏向锁的线程A并检查程A的状态;

如果线程A不处于活动状态或者已经退出同步代码块,则将对象锁设置为无锁状态,然后再升级为轻量级锁。由线程B获取轻量级锁。

如果线程A还在执行同步代码块,也就是线程A还需要这个对象锁,则偏向锁膨胀为轻量级锁。

线程A膨胀为轻量级锁过程:

在升级为轻量级锁之前,持有偏向锁的线程(线程A)是暂停的

线程A栈帧中创建一个名为锁记录的空间(Lock Record)

锁对象头中的Mark Word拷贝到线程A的锁记录中

Mark Word的锁标志位变为00,指向锁记录的指针指向线程A的锁记录地址,Mark word(锁标志位-00,其他位-线程A锁记录的指针)

当原持有偏向锁的线程(线程A)获取轻量级锁后,JVM唤醒线程A,线程A执行同步代码块

线程A持有轻量级锁,线程A执行完同步块代码之后,一直没有线程来竞争对象锁,正常释放轻量级锁。释放轻量级锁操作:CAS操作将线程A的锁记录(Lock Record)中的Mark Word替换回锁对象头中。

线程A持有轻量级锁,执行同步块代码过程中,线程B来竞争对象锁。

线程B会先在栈帧中建立锁记录,存储锁对象目前的Mark Word的拷贝

线程B通过CAS操作尝试将锁对象的Mark Word的指针指向线程B的Lock Record,如果成功,说明线程A刚刚释放锁,线程B竞争到锁,则执行同步代码块。

因为线程A一直持有锁,大部分情况下CAS是会失败的。CAS失败之后,线程B尝试使用自旋的方式来等待持有轻量级锁的线程释放锁。

- 版权声明 - 1、本帖所有言论和图片等纯属网友个人意见,与流星社区立场无关;
2、其他单位或个人使用、转载或引用本帖时必须同时征得该帖子作者知安流星社区的同意;
3、备注原文地址:https://bbs.liuxingw.com/t/17991.html,可忽略第2条;
4、帖子作者需承担一切因本文发表而直接或间接导致的相关责任;
5、如本帖内容或部分内容转载自其它媒体,这并不代表本站赞同其观点和对其真实性负责;
6、如本帖若为资源类,将仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您安装或使用的设备中彻底删除上述内容;
7、如果您喜欢该程序,请支持正版软件,购买注册,可以得到更好的正版服务;
8、如本帖侵犯到任何版权或违法问题,请立即邮件告知我们,我们将及时予以处理。
3条回复 |  最后回复于2019-11-21

知安 [1级]

补充:线程B不会一直自旋下去,如果自旋了一定次数后还是失败,线程B会被阻塞,等待释放锁后唤醒。此时轻量级锁就会膨胀为重量级锁。Mark word(锁标志位-10,其他位-重量级锁monitor的指针)

线程A执行完同步块代码之后,执行释放锁操作,CAS 操作将线程A的锁记录(Lock Record)中的Mark Word 替换回锁对象对象头中,因为对象头中已经不是原来的轻量级锁的指针了,而是重量级锁的指针,所以CAS操作会失败。

释放轻量级锁CAS操作替换失败之后,需要在释放锁的同时需要唤醒被挂起的线程B。线程B被唤醒,获取重量级锁monitor

总结

synchronized实现同步会严重影响到程序效率,为了减少重量级锁带来的性能开销,JDK对Synchronized进行了优化。

自旋锁:当锁被占用时,当前想要获取锁的线程不会被立即挂起,而是做几个空循环,看持有锁的线程是否会很快释放锁。如果此时锁释放,当前线程就可以获得锁。

锁消除:如果JVM检测到某段代码不可能存在共享数据竞争,会对这段代码的同步锁进行锁消除。

锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

synchronized锁膨胀过程就是无锁 → 偏向锁 → 轻量级锁 → 重量级锁的一个过程。这个过程是随着多线程对锁的竞争越来越激烈,锁逐渐升级膨胀的过程。
发布于2019-11-21

回复列表

  • 内容加载中...

说点什么...

安然 [3级]

想屁吃
发布于2019-11-21

回复列表

  • 内容加载中...

说点什么...

[]

看不懂[抱枕]
发布于2019-11-21

回复列表

  • 内容加载中...

说点什么...
登录注册 后才可进行评论
签到
38人签到
已签0天
  • 46637帖子
  • 1936934热点量
  • 185039火热值