synchronized优化
重量级锁
前文解释了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优化的更多相关文章
- synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...
synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇中我们谈到了锁膨胀对 synchronized 性能的提升,然 ...
- Java基础之(一)——从synchronized优化看Java锁概念
一.悲观锁和乐观锁概念 悲观锁和乐观锁是一种广义的锁概念,Java中没有哪个Lock实现类就叫PessimisticLock或OptimisticLock,而是在数据并发情况下的两种不同处理策略. 针 ...
- synchronized 优化手段之锁膨胀机制!
synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized.然而这个情况在 JDK 1.6 时就发生了改变,JDK 1.6 ...
- Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)
不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...
- ReenTrantLock可重入锁(和synchronized的区别)总结
ReenTrantLock可重入锁(和synchronized的区别)总结 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也 ...
- Synchronized与ReentrantLock区别总结(简单粗暴,一目了然)
这篇文章是关于这两个同步锁的简单总结比较,关于底层源码实现原理没有过多涉及,后面会有关于这两个同步锁的底层原理篇幅去介绍. 相似点:这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的 ...
- synchronized底层实现学习
上文我们总结了 synchronized 关键字的基本用法以及作用,并未涉及 synchronized 底层是如何实现的,所谓刨根问底,本文我们就开始 synchronized 原理的探索之旅吧(*& ...
- 多线程深入:让你彻底理解Synchronized(转)
原文:https://www.jianshu.com/p/d53bf830fa09 1. synchronized简介 在学习知识前,我们先来看一个现象: public class Synchroni ...
- (转)synchronized和lock的区别
背景:最近在准备java基础知识,对于可重入锁一直没有个清晰的认识,有必要对这块知识进行总结. 1 . 什么是可重入锁 锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保 ...
随机推荐
- 【JAVAEE学习笔记】hibernate03:多表操作详解、级联、关系维护和练习:添加联系人
一.一对多|多对一 1.关系表达 表中的表达 实体中的表达 orm元数据中表达 一对多 <!-- 集合,一对多关系,在配置文件中配置 --> <!-- name属性:集合属性名 co ...
- 【2017-06-06】Ajax完整结构、三级联动的制作
一.Ajax完整结构 $.ajax({ url:"Main.ashx", data:{}, dataType:"json", type:"post&q ...
- NLTK学习笔记(三):NLTK的一些工具
主要总结一下简单的工具:条件频率分布.正则表达式.词干提取器和归并器. 条件分布频率 <自然语言学习>很多地方都用到了条件分布频率,nltk提供了两种常用的接口:FreqDist 和 Co ...
- 链表插入和删除,判断链表是否为空,求链表长度算法的,链表排序算法演示——C语言描述
关于数据结构等的学习,以及学习算法的感想感悟,听了郝斌老师的数据结构课程,其中他也提到了学习数据结构的或者算法的一些个人见解,我觉的很好,对我的帮助也是很大,算法本就是令人头疼的问题,因为自己并没有学 ...
- Ubuntu安装搜狗拼音和金山快盘
搜狗拼音和金山快盘是UbuntuKylin中的特色中文应用,通过下述方法在Ubuntu或UbuntuKylin中安装. 一.更新Fcitx 1.由于 Ubuntu 12.04自带的 小企鹅输入法Fci ...
- python实现微信接口(itchat)
python实现微信接口(itchat) 安装 sudo pip install itchat 登录 itchat.auto_login() 这种方法将会通过微信扫描二维码登录,但是这种登录的方式确实 ...
- GA代码中的细节
GA-BLX交叉-Gaussion变异 中的代码细节: 我写了一个GA的代码,在2005测试函数上一直不能得到与实验室其他同学类似的数量级的结果.现在参考其他同学的代码,发现至少有如下问题: 1.在交 ...
- 学容器必须懂 bridge 网络 - 每天5分钟玩转 Docker 容器技术(32)
上一节我们讨论了 none 和 host 类型的容器网络,本节学习应用最广泛也是默认的 bridge 网络. Docker 安装时会创建一个 命名为 docker0 的 linux bridge.如果 ...
- java web数据库(SQL 2008+IDEA 14)环境配置
废话少说,在之前已经配置过IDEA+Tomcat的环境之后,现在需要进行数据库配置: 1.首先,SQL SERVER2008数据库的安装 (1)将下载的sqlserver 2008数据库进行解压,点击 ...
- jquery fadeIn用法
$("#msgSpan").fadeIn("slow"); setTimeout('$("#msgSpan").hide("slo ...