重量级锁

前文解释了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. ASP.NET MVC5(二):控制器、视图与模型

    前言 本篇博文主要介绍ASP.NET MVC中的三个核心元素:控制器.视图与模型,以下思维导图描述了本文的主要内容. 控制器 控制器简介 在介绍控制器之前,简单的介绍一下MVC工作原理:URL告知路由 ...

  2. Spring学习(1)----入门学习(附spring-framework下载地址)

    (一)Spring是什么 Spring是一个开源框架,为了解决企业应用开发的复杂性而创建的,但现在已经不止应用于企业应用 是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架- 从大小和开销 ...

  3. Intel VT-x 处于禁用状态

    出现错误"此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态"的问题,如下图. Intel VT-x 即Virtualization Technology, ...

  4. hibernate查询部分字段转换成实体bean

    //hibernate查询部分字段转换成实体bean /** * 查询线路信息 */ @Override public List<Line> getSimpleLineListByTj(M ...

  5. Linux常用操作命令(三)

    查看linux日志某几行 用逆序显示命令tail查看 命令格式:tail [  -r ] [  -n Number ] [ File ] [一]从第3000行开始,显示1000行.即显示3000~39 ...

  6. django-xadmin数字输入框不支持小数点小数问题

    环境:https://github.com/y2kconnect/xadmin-for-python3.git python3.5.2 django1.9.12 原因:数字输入框用的是html5 in ...

  7. [leetcode-566-Reshape the Matrix]

    In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new o ...

  8. Example002定时打开窗口

    <!--实例002定时打开窗口--> <script> // 3秒后弹出窗口: function time() { window.open("index.html&q ...

  9. 复写equals、hashCode和toString方法

    equals.hashCode和toString 这三个方法都是object类的方法,由于所有的类都是继承这个类,所以每一个类都有这三个方法. 1.复写equals方法 原则: 首先,两个实例是相同的 ...

  10. canvas学习总结五:线段的端点与连接点

    我们在第三节中描述了线段的绘制,其中线段的属性lineWidth是用来改变线段的宽度.让我们来回忆下线宽的用法 function drawLine(){ cxt.lineWidth = 3; cxt. ...