首先说一下锁的优化策略。

1,自旋锁

自选锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。这个问题是基于一个现实考量的:很多拿了锁的线程会很快释放锁。因为一般敏感的操作不会很多。当然这个是一个不能完全确定的情况,只能说总体上是一种优化。

举个例子就好比一个人要上厕所发现厕所里面有人,他可以:1,等一小会。2,跑去另外的地方上厕所。等一小会不一定能等到前一个人出来,不过如果跑去别的厕所的花费的时间肯定比等一小会结果前一个人出来了长。当然等完了结果那个人没出来还是要跑去别的地方上厕所这是最慢的。

然后是基于这种做法的一个优化:自适应自旋锁。也就是说,第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。道理是:一个锁如果能够在自旋的过程中被释放说明很有可能下一次也会发生这种事。那么就更要给这个锁某种“便利”方便其不阻塞得锁(毕竟快了很多)。同样如果多次尝试的结果是完全不能自旋等到其释放锁,那么就说明很有可能这个临界区里面的操作比较耗时间。就减小自旋的次数,因为其可能性太小了。

2,锁粗化

试想有一个循环,循环里面是一些敏感操作,有的人就在循环里面写上了synchronized关键字。这样确实没错不过效率也许会很低,因为其频繁地拿锁释放锁。要知道锁的取得(假如只考虑重量级MutexLock)是需要操作系统调用的,从用户态进入内核态,开销很大。于是针对这种情况也许虚拟机发现了之后会适当扩大加锁的范围(所以叫锁粗化)以避免频繁的拿锁释放锁的过程。

3,锁消除

通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。

4,偏向锁和轻量级锁

这两个锁既是一种优化策略,也是一种膨胀过程所以一起说。首先它们的关系是:最高效的是偏向锁,尽量使用偏向锁,如果不能(发生了竞争)就膨胀为轻量级锁,这样优化的效率不如原来高不过还是一种优化(对比重量级锁而言)。所以整个过程是尽可能地优化。

首先说说偏向锁。

HotSpot的研究人员发现大多数情况下虽然加了锁,但是没有竞争的发生,甚至是同一个线程反复获得这个锁。那么偏向锁就为了针对这种情况。

举个例子,一个仓库管理员管着钥匙,然而每一次都是老王去借,仓库管理员于是就认识了老王,直接和他说,“行,你直接拿就是不用填表格了我记得你”。

讲一下偏向锁的具体过程。首先JVM要设置为可用偏向锁。然后当一个进程访问同步块并且获得锁的时候,会在对象头和栈帧的锁记录里面储存取得偏向锁的线程ID。

下一次有线程尝试获取锁的时候,首先检查这个对象头的MarkWord是不是储存着这个线程的ID。如果是,那么直接进去而不需要任何别的操作。如果不是,那么分为两种情况。1,对象的偏向锁标志位为0(当前不是偏向锁),说明发生了竞争,已经膨胀为轻量级锁,这时使用CAS操作尝试获得锁(这个操作具体是轻量级锁的获得锁的过程下面讲)。2,偏向锁标志位为1,说明还是偏向锁不过请求的线程不是原来那个了。这时只需要使用CAS尝试把对象头偏向锁从原来那个线程指向目前求锁的线程。这种情况举个例子就是老王准备退休了,他儿子接替他来拿钥匙,于是仓库管理员认识了他儿子,他儿子每次来也不用登记注册了。

这个CAS失败了呢?首先必须明确这个CAS为什么会失败,也就是说发生了竞争,有别的线程和它抢锁并且抢赢了,那么这个情况下,它就会要求撤销偏向锁(因为发生了竞争)。接着它首先暂停拥有偏向锁的线程,检查这个线程是否是个活动线程,如果不是,那么好,你拿了锁但是没在干事,锁还记录着你,那么直接把对象头设置为无锁状态重新来过。如果还是活动线程,先遍历栈帧里面的锁记录,让这个偏向锁变为无锁状态,然后恢复线程。

再说轻量级锁。这是偏向锁膨胀之后的产物。

加锁的过程:JVM在当前线程的栈帧中创建用于储存锁记录的空间(LockRecord),然后把MarkWord放进去,同时生成一个叫Owner的指针指向那个加锁的对象,同时用CAS尝试把对象头的MarkWord成一个指向锁记录的指针。成功了就拿到了锁。那么失败了呢?失败了的说法比较多。主流有《深入理解JVM》的说法和《并发编程的艺术》的说法。

《深入理解JVM》的说法:

失败了,去查看MarkWord的值。有2种可能:1,指向当前线程的指针,2,别的值。

如果是1,那么说明发生了“重入”的情况,直接当做成功获得锁处理。

其实这个有个疑问,为什么获得锁成功了而CAS失败了,这里其实要牵扯到CAS的具体过程:先比较某个值是不是预测的值,是的话就动用原子操作交换(或赋值),否则不操作直接返回失败。在用CAS的时候期待的值是其原本的MarkWord。发生“重入”的时候会发现其值不是期待的原本的MarkWord,而是一个指针,所以当然就返回失败,但是如果这个指针指向这个线程,那么说明其实已经获得了锁,不过是再进入一次。如果不是这个线程,那么情况2:

如果是2,那么发生了竞争,锁会膨胀为一个重量级锁(MutexLock)

《并发编程的艺术》的说法:

失败了直接自旋。期望在自旋的时间内获得锁,如果还是不能获得,那么开始膨胀,修改锁的MarkWord改为重量级锁的指针,并且阻塞自己。

解锁过程:(那个拿到锁的线程)用CAS把MarkWord换回到原来的对象头,如果成功,那么没有竞争发生,解锁完成。如果失败,表示存在竞争(之前有线程试图通过CAS修改MarkWord),这时要释放锁并且唤醒阻塞的线程。

JAVA锁的膨胀过程和优化的更多相关文章

  1. JAVA锁的膨胀过程和优化(阿里)

    阿里的人问什么是锁膨胀,答不上来,回来做了总结: 关于锁的膨胀,synchronized的原理参考:深入分析Synchronized原理(阿里面试题) 首先说一下锁的优化策略. 1,自旋锁 自旋锁其实 ...

  2. java并发笔记之四synchronized 锁的膨胀过程(锁的升级过程)深入剖析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是锁的升 ...

  3. synchronized(三) 锁的膨胀过程(锁的升级过程)深入剖析

    警告⚠️:本文耗时很长,先做好心理准备................哈哈哈 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是 ...

  4. JAVA锁的优化和膨胀过程

    转自:https://www.cnblogs.com/dsj2016/p/5714921.html https://cloud.tencent.com/developer/article/103675 ...

  5. java架构之路(多线程)synchronized详解以及锁的膨胀升级过程

    上几次博客,我们把volatile基本都说完了,剩下的还有我们的synchronized,还有我们的AQS,这次博客我来说一下synchronized的使用和原理. synchronized是jvm内 ...

  6. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  7. Java 锁优化

    一.重量级锁   Java中,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的.而操作系统实现 ...

  8. synchronized的实现原理——锁膨胀过程

    @ 目录 前言 正文 偏向锁 轻量锁 批量重偏向 批量撤销 重量锁 总结 前言 上一篇分析了优化后的synchronized在不同场景下对象头中的表现形式,还记得那个结论吗?当一个线程第一次获取锁后再 ...

  9. 【转】Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

     一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本 ...

随机推荐

  1. IOS开发基础知识--碎片10

    1:如何给表格单元列增加选择时的背影效果 if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCel ...

  2. android 传感器应用

    本章讲述Android开发中,传感器应用相关的知识点. 1.功能需求 做一个基于传感器的水平尺应用. 2.软件实现

  3. [css]我要用css画幅画(六)

    接着之前的[css]我要用css画幅画(五), 由于这个画已经画了很久了,这次一次性加了比较多更新,算是让这幅画告一段落吧. 本次的更新包括: 1. 给云增加动画 2. 画了一棵树 3. 树上画了一个 ...

  4. Oracle创建表空间和用户

    用户名为:C##NEWO ============================================================================= /*分为四步 */ ...

  5. Centos 安装jdk1.8

    我是根据右边链接进行安装的 ,但是第一步不同噢.http://www.cnblogs.com/spiders/archive/2016/09/06/5845727.html 1.下载rpm安装文件. ...

  6. CentOS配置Nginx+Tomcat7的多站点支持

    注:以下所有操作均在CentOS 6.5 x86_64位系统下完成. #准备工作# 在这之前需要先把Nginx和Tomcat安装好,具体见:<CentOS安装Nginx-1.6.2+安全配置&g ...

  7. 编程中Foo, Bar 到底什么意思?

    1 前言 在很多国外计算机书本和一些第三份开源软件的Demo中经常用到两个英文单词Foo,Bar.这到底是什么意思呢?从步入屌丝界的IT生活见到这两个单词到现在我还是不知道这两个单词的真正含义,今天有 ...

  8. IIS+域组策略+hosts:禁止访问指定网站

    一.简介 禁止访问网站可以通过多种方式实现,在网络设备上实现大概是性能最好的方式.本文在域服务器上实现该功能,优点是配置简单.可自定义跳转页面,缺点也很明显,遇到熟悉操作系统的用户,修改hosts文件 ...

  9. Linux下安装Nginx1.9.3

    操作系统:CentOS6.5 64bit Nginx: 1.9.3 1.下载Nginx [root@iZ94jj63a3sZ softs]# wget http://nginx.org/downloa ...

  10. gdb进程调试,多进程调试

    1.单进程的调试 常规的通过gdb cmd这种方式开启调试,特别说明的是通过attach的方法附加到一个指定的进程上去进行调试,这种方法适合于调试一个已经运行的进程,具体用法:  gdb -p [pi ...