重量级锁

前文解释了synchronized的实现和运用,了解monitor的作用,但是由于monitor监视器锁的操作是基于操作系统的底层Mutex Lock实现的,对所要加锁线程加上互斥锁,但是加锁时间相比其他指令就长很多了,因此将这种基于互斥锁的加锁机制成为重量级锁。

而在JDK1.6之后,对synchronized优化,根据不同情形出现了偏向锁、轻量锁、对象锁,自旋锁(或自适应自旋锁)等,因此,现在的synchronized可以说是一个几种锁过程的封装。

为了更好地解释下面几种锁,这里需要描述一下synchronized的线程排队和锁标志位

对象头

了解Java虚拟机知道Java的对象是创建在堆上的,指向堆的引用才放在栈上,而在堆上创建的对象结构大致是这样的



其中对象头就是用于保存对象的信息,包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,在不同状态的情况下,对象头内容不同



这里我们主要关注的是锁标志位,当一个线程获取对象并加锁后,标志位从01变为10,其他线程则处于排队状态。



图中可以看出,当存在调用对象被运行的线程占用事,请求线程会进入排队队列,还有wait之后notify的线程。

自旋锁(自适应锁)

由于线程阻塞后进入排队队列和唤醒都需要CPU从用户态转为核心态,花费时间较多,尤其频繁的阻塞和唤醒对CPU来说也是负荷很重的工作,同时,统计发现很多线程锁定状态只持续很短时间,如果这时候其他线程进入等待队列之后再唤醒太费时间了,因此,出现了自旋锁。

自旋锁,由于线程阻塞和唤醒的代价比较大,对于等待的线程,不先加到等待队列中,而是去执行一个无意义的循环,一直到运行的线程结束之后去竞争锁。但是明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平,而且CPU在等待自旋锁时不做任何有用的工作,仅仅是等待,浪费资源,但是这样可以保证大吞吐率和执行效率。但是由于CPU的自旋消耗比较大,因此自旋是有范围的,超过这个范围就会进入排队队列,即重量级锁的机制

自适应自旋锁,就是自旋的次数是通过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。

轻量级锁和偏向锁

在某些情况下,synchronized区域不存在竞争,依然按照重量级锁的方式运行,会无端消耗资源,因此JDK1.6之后,加入了轻量锁和偏向锁。

不过,需要注意的是轻量锁或者偏向锁不能代替重量级锁的功能,只是在无竞争环境下,减少无端消耗,如果出现竞争,还是会转换成重量级锁。

轻量级锁

在前面已经描述了Java对象头的结构,对于除了锁标志位的部分,被定义为Mark Word,图中可以发现,不同锁状态的Mark Word内容是不同,这也是对Mark Word的运用,这里主要讲无锁状态和轻量级锁状态的转换。

加锁

  • 在运行到同步块的时候,如果锁标志位为“01”状态,是否偏向锁为“0”(无锁状态),那么虚拟机当前线程的栈帧中会建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为Displaced Mark Word
  • 拷贝对象头中的Mark Word复制到锁记录中,然后通过CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,将Lock record里的owner指针指向object mark word。CAS操作是用来保证多线程情况下操作原子性的方法,即compare and set,比较成功后再操作,这里就不详细讲了。
  • 如果更新成功,那么线程获取同步块锁并将对象头的锁标志位改为00,操作如图



  • 如果失败,那么先检查CAS操作是否成功,如果成功,那么表示锁已经获得,直接执行即可,如果没有,就说明有竞争,那么就需要升级到重量级锁。

整个流程可以由下图表示

偏向锁

偏向锁是比轻量级锁更轻量的锁,在无竞争情况下,使用轻量锁还是需要CAS操作进行信息交换等消耗一定资源,有的时候,同步块可能只被一个线程占用,那么甚至不需要CAS交换信息,只要做标志位即可,偏向锁就是这么做的。

加锁

  • 当同步块的对象头处于无锁状态时,把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。
  • 操作成功后持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查ThreadId是否和自身线程Id一致。
  • 如果一致,那么表示线程已经获取了锁,那么直接执行即可,不需要加锁。
  • 如果不一致,表示有其他的线程访问同步块了,此时需要判断对象头状态,如果此时处于无锁状态,那么就执行最开始的操作,将锁转移给该线程,如果仍然是偏向锁状态,那么转变成轻量锁。

解锁

  • 线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程。

整个偏向锁和升级轻量锁的过程如图

其他优化

除了以上因为不同竞争状态优化的锁,还有一些因为特殊应用场景的优化。

锁粗化

锁粗化就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。

StringBuffer str = new StringBuffer();
str.append("1");
str.append("2");
str.append("3");

这里由于StringBuffer内部是有锁的,在执行append()的时候原来是需要加锁解锁三次的,这里锁的粗化将三个锁合并成一个,最开始加锁之后最后再解锁。

锁消除

锁消除就是虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除,即对于代码数据的逃逸分析,如果数据无法逃逸并且私有的话,锁其实是没必要的,可以消除。

比如上面的StringBuffer变量,如果被放在有个私有函数中作为中间值,不被输出,那个根本不存在数据逃逸,就不需要加锁。

synchronized优化的更多相关文章

  1. synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...

    synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇中我们谈到了锁膨胀对 synchronized 性能的提升,然 ...

  2. Java基础之(一)——从synchronized优化看Java锁概念

    一.悲观锁和乐观锁概念 悲观锁和乐观锁是一种广义的锁概念,Java中没有哪个Lock实现类就叫PessimisticLock或OptimisticLock,而是在数据并发情况下的两种不同处理策略. 针 ...

  3. synchronized 优化手段之锁膨胀机制!

    synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized.然而这个情况在 JDK 1.6 时就发生了改变,JDK 1.6 ...

  4. Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)

    不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...

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

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

  6. Synchronized与ReentrantLock区别总结(简单粗暴,一目了然)

    这篇文章是关于这两个同步锁的简单总结比较,关于底层源码实现原理没有过多涉及,后面会有关于这两个同步锁的底层原理篇幅去介绍. 相似点:这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的 ...

  7. synchronized底层实现学习

    上文我们总结了 synchronized 关键字的基本用法以及作用,并未涉及 synchronized 底层是如何实现的,所谓刨根问底,本文我们就开始 synchronized 原理的探索之旅吧(*& ...

  8. 多线程深入:让你彻底理解Synchronized(转)

    原文:https://www.jianshu.com/p/d53bf830fa09 1. synchronized简介 在学习知识前,我们先来看一个现象: public class Synchroni ...

  9. (转)synchronized和lock的区别

    背景:最近在准备java基础知识,对于可重入锁一直没有个清晰的认识,有必要对这块知识进行总结. 1 . 什么是可重入锁 锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保 ...

随机推荐

  1. go服务端----使用dotweb框架搭建简易服务

    使用dotweb框架搭建简易服务 go语言web框架挺多的,所谓琳琅满目,里面也有很多优秀的,比如echo.beego等,但体验下来,总是觉得哪里有点小疙瘩,后来才明白过来,echo太简单,很多日常使 ...

  2. 磁盘配额quota

    磁盘配额 1 启用磁盘配额 首先创建新的分区 /dev/sd5,并创建文件系统. [root@local ~]# mkfs.ext4 /dev/sda5 由于xfs 不磁盘配额能成功,这里使用ext4 ...

  3. 在ie下,a标签包被img的时候,为什么有个蓝色的边线

    效果像下图这样 那是由于<img>在ie下有默认边框,只要清除边框就可以了,在style中定义 img{ border:none } 显示效果就变成下面这样了 完!

  4. redis学习(1)--- NoSQL介绍

    一.NoSQL介绍 1.什么是NoSQL NoSQL = Not Only SQL 非关系型数据库 2.为什么用NoSQL High performance - 高并发读写 Huge Storage ...

  5. http服务器开发笔记(一)——先跑起来

    做了很多年的web相关开发,从来也没有系统的学习http协议,最近正好工作不怎么忙,准备系统的学习一下. 接下来准备自己写一小型的http服务器来学习,因为现在对JavaScript比较熟悉,所以决定 ...

  6. .Net Core 图片文件上传下载

    当下.Net Core项目可是如雨后春笋一般发展起来,作为.Net大军中的一员,我热忱地拥抱了.Net Core并且积极使用其进行业务的开发,我们先介绍下.Net Core项目下实现文件上传下载接口. ...

  7. Java(14)继承

    1.继承(extends) 1.1 继承:遗传 1.2 语法 public class 子类 extends 父类{ } public class Dog extends Pet{ } public ...

  8. MyBatis介绍

    MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索.MyBatis 使用简单的 XML或注解用于配 ...

  9. python 标准库 -- os

    os os.getcwd() os.getcwd() # 获取当前工作目录 os.listdir(path) os.listdir('/tmp') # 列出指定目录下的文件和目录 os.mkdir(p ...

  10. Ubuntu16.04更换漂亮绚丽flatabulous主题

    作者:tongqingliu 转载请注明出处: Ubuntu16.04更换漂亮绚丽flatabulous主题 更新 sudo apt-get update sudo apt-get upgrade 安 ...