重量级锁

前文解释了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. 【Web开发】Mean web开发 01-Express实现MVC模式开发

    简介 Mean是JavaScript的全栈开发框架.更多介绍 用Express实现MVC模式开发是Mean Web全栈开发中的一部分. Express 是一个基于 Node.js 平台的极简.灵活的 ...

  2. [codeforces113D]Museum

    D. Museum time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input ...

  3. vue-cli创建自己的项目

    vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目,GitHub地址是:https://github.com/vuejs/vue-cli 一. ...

  4. Bash的作业控制

    作业控制是bash Shell提供的一项强大功能,它允许你选择在前台还是后台运行程序,即作业. 1.开启bash的作业控制功能 #set -o monitor或#set -m 2.显示在后台运行的作业 ...

  5. [平衡树] mingap

    时间限制: 1 Sec  内存限制: 128 MB提交: 18  解决: 9 题目描述 实现一种数据结构,维护以下两个操作: (1) I x :加入元素 x : (2) M :输出当前表中相差最小的两 ...

  6. flask简单web应用

    推荐一个学习python的网站,个人觉得在这里面收获挺大的,希望对后来学习flask的小伙伴们有帮助.http://www.pythondoc.com/ 用flask框架实现第一个web应用 首先需要 ...

  7. 新安装mysql 第三方工具连接不上问题

    Mysql从客户端连接服务器连不上的问题   公司要用Mysql做一个测试,开始在自己的本地建一个Mysql数据库自己本地的程序再连上去,没有遇到过连接不上的问题.这次数据库在服务器上,从本地客户端连 ...

  8. [leetcode-474-Ones and Zeroes]

    In the computer world, use restricted resource you have to generate maximum benefit is what we alway ...

  9. 【Android Developers Training】 70. 使用ViewPager实现屏幕滑动

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  10. 在Visual Studio 2017中使用Asp.Net Core构建Angular4应用程序

    前言 Visual Studio 2017已经发布了很久了.做为集成了Asp.Net Core 1.1的地表最强IDE工具,越来越受.NET系的开发人员追捧. 随着Google Angular4的发布 ...