重量级锁

前文解释了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. FTP主动模式和被动模式的区别

    基础知识: FTP只通过TCP连接,没有用于FTP的UDP组件.FTP不同于其他服务的是它使用了两个端口, 一个数据端口和一个命令端口(或称为控制端口).通常21端口是命令端口,20端口是数据端口.当 ...

  2. WPF界面XAML中的if……else……

    xaml本身并不支持if--else--,要用Converter替代if--else--来实现我们想要的效果,知者请速离开,不要浪费时间   需求:按照Window的WindowState来决定Gri ...

  3. mybatis批量增、删、改(更新)操作oracle和mysql批量写法小记

    前言:用mybatis也好几年了,mybatis在批量的增删操作也写起来也是比较简单的,只有批量更新这一块是特别坑,特此记录. 注:本文主要用来记录oracle和mysql数据库在使用mybatis的 ...

  4. loadrunner学习理论之一

    1.负载测试.压力测试的区别? 答:负载测试是在被测系统所承受的正常范围内进行的 压力测试可以在极端的条件下进行 2.loadrunner的三大组件是什么,有什么作用? 答:虚拟用户生成器(virtu ...

  5. oracle日志挖掘

    oracle日志挖掘是一种十分强大的数据恢复技术,只要你保障你的归档日志和重做日志是完整的,那么就可以将你的数据恢复到任何时刻.简单叙述一下日志挖掘的基本原理,然后进行一个简单的小实验. 日志挖掘时基 ...

  6. JDBC加载数据库驱动的方式

    JDBC作为数据库访问的规范接口,其中只是定义一些接口.具体的实现是由各个数据库厂商来完成. 一.重要的接口: 1.public interface Driver 每个驱动程序类必须实现的接口.Jav ...

  7. XAML: 获取元素的位置

    在之前讨论 ListView 滚动相关需求的文章中(UWP: ListView 中与滚动有关的两个需求的实现)曾经提到了获取元素相对位置的方法,即某元素相对另一元素的位置.现将所有相关方法再作整理,并 ...

  8. “Failed to access IIS metabase”解决方法

    原因:IIS没有注册解决办法:在CMD中进入目录C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727,运行aspnet_regiis1.aspnet_regiis ...

  9. Java学习笔记--反射API

    反射API 1.反射API的介绍 通过反射API可以获取Java程序在运行时刻的内部结构.比如Java类中包含的构造方法.域和方法等元素,并可以与这些元素进行交换.     按照 一般地面向对象的设计 ...

  10. Vulkan Tutorial 23 Descriptor layout and buffer

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 我们现在可以将任意属性传递给每个顶点的顶点着色器使用.但是 ...