高并发编程-锁优化详解

Java在语言上支持了锁的特性,在很多常用类的实现中也使用了锁,对于Java开发者来说就可以很方便的使用这些锁及常用类。但是,随着锁的频繁使用及错用,随之而来的就是程序执行效率变低、应用变的缓慢。为了提高线程对共享数据访问的效率,HotSpot虚拟机从JDK1.5到JDK1.6做了重大改进,提供了很多锁优化技术,包括自旋锁、自适应自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。

自旋锁

线程的执行是通过竞争获取处理器的执行时间才执行的。当线程挂起或恢复执行的时,会从用户态转入内核态中完成,这种操作是很消耗时间的,在并发情况下对应用和系统来说都有很大压力。HotSpot虚拟机的开发人员注意到很多应用对共享数据锁定的时间都是很短暂的,为了这短暂的时间而挂起和恢复线程是不值得的。所以,线程并发请求锁的时候,让后来的线程在不放弃处理器执行时间的情况下稍等一下,线程做自旋,自旋期间观察持有锁的线程是否会很快释放锁,这种技术就是所谓的自旋锁。

自旋锁在JDK1.6中是默认开启的,默认自旋次数是10次,可以使用参数-XX:PreBlockSpin修改默认值。虽然,自旋锁避免了线程挂起和恢复的开销,但是它占用了处理器的执行时间,如果锁占用时间很短,自旋锁效果很好,否则会浪费处理器的执行时间,影响应用的整体性能。

自适应自旋锁

在JDK1.6中引入了自适应自旋锁,自旋的次数由上一次在同一个锁上自旋的时间和锁持有者的状态来决定。如果上一次同一个锁通过自旋刚刚被获取,并且持有锁的线程正在运行,那么虚拟机认为本次自旋也会成功,将会自旋相对长的时间获取锁。如果同一个锁很少通过自旋成功被获取,那么虚拟机认为本次自旋也会失败,不会执行自旋操作。

锁消除

一些使用了锁控制的代码,在虚拟机即时编译器运行时检测到不存在对共享数据的竞争访问,也就是代码只会被一个线程访问,此时会对锁进行消除,这项优化称为锁消除。锁消除的主要判断依据来源于逃逸分析(即分析对象的动态作用域,一个对象在方法内被定义后,在别的方法或线程中无法通过任何途径访问到这个对象,则可以进行一些优化操作)的数据支持。

锁粗化

大多数情况下,为了提高程序的执行效率,会缩小锁作用的范围。但是,对于一些连续操作都对同一个对象进行反复加锁、释放锁的情况来说,缩小锁的作用范围会消耗更多的资源,这种情况需要扩大锁的作用范围,这项优化称为锁粗化。

轻量级锁

了解轻量级锁之前,先了解一下Java对象在内存中存储的数据结构。在HotSpot虚拟机中,Java对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐填充。对象头包含两部分,第一部分包含对象的HashCode、分代年龄、锁标志位、线程持有的锁、偏向线程ID等数据,这部分数据的长度在32位和64位虚拟机中分别为32bit和64bit,官方称为Mark World。考虑到虚拟机的空间效率,Mark World内部的数据结构是非固定的,也就是说对象头中存储的内容是不固定的,下图展示了不同状态下,对象头中存储的内容:

当代码执行到同步代码时,如果此时对象的锁未被锁定(锁标志位位01),虚拟机将在当前线程的栈帧中创建一个名为Lock Record空间,这个空间用于存储当前对象的Mark World拷贝,具体如下图所示。

接着,虚拟机使用CAS尝试将对象的对象头Mark Wolrd指向Lock Record,也就是在Mark Wolrd的30bit存储Lock Record的起始地址,具体如下图所示。如果上述操作执行成功,当前线程就持有了对象的锁,此时对象处于轻量级锁锁定状态,对应的锁标志位为00。

如果上述操作执行失败,首先会检查对象的对象头Mark World是否指向了当前线程栈帧中的Lock Record,如果指向了则表示当前线程已经持有了对象的锁,否则表示对象的锁已经被其它线程持有,锁膨胀为重量级锁,线程挂起等待。

轻量级锁的释放过程,通过CAS将Lock Record中存储的Mark Wolrd拷贝替换回对象的对象头Mark Wolrd中,替换成功则锁释放成功,否则表示有其它线程尝试获取过锁,释放锁的同时,唤醒挂起的线程,这里笔者的理解是此时锁膨胀为重量级锁,唤醒等待线程竞争。

偏向锁

锁对象第一次被线程持有的时候,虚拟机通过CAS把获取到这个锁的线程ID记录到对象头Mark World中,操作成功则成功获取偏向锁,对象头中的锁标志位设置为01。持有偏向锁的线程每次执行到这段同步代码时,不需要任何同步操作,这项优化称为偏向锁。

当有其它线程尝试获取对象的锁时,终止偏向模式,同时根据锁是否处于锁定状态,撤销偏向锁恢复到未锁定或轻量级锁状态。

原文发布于微信公众号 - JavaQ(Java-Q)

原文发表时间:2018-10-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

注册中心 Eureka 源码解析 —— 网络通信

本文主要分享 Eureka 的网络通信部分。在不考虑 Eureka 2.x 的兼容的情况下,Eureka 1.x 主要两部分的网络通信:

1092
来自专栏程序猿DD

Hystrix降级逻辑中如何获取触发的异常?

通过之前Spring Cloud系列教程中的《Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)》一文,我们已经知道如何通过Hyst...

1343
来自专栏沃趣科技

MySQL中server_id一致带来的问题

简 介 我们都知道在MySQL搭建复制环境的时候,需要设置每个server的server_id不一致,如果主库与从库的server_id一致,那么复制会失败。...

3896
来自专栏Java3y

纳税服务系统一(用户模块)【简单增删改查、日期组件、上传和修改头像】

前言 为了更好地掌握SSH的用法,使用一个纳税服务系统来练手…..搭建SSH框架环境在上一篇已经详细地说明了。http://blog.csdn.net/hon_...

6059
来自专栏weixuqin 的专栏

使用 intellijIDEA 创建 maven 工程进行 Spring ioc 测试

控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖...

1004
来自专栏xingoo, 一个梦想做发明家的程序员

Spring MVC 基于Method的映射规则(注解版)

在Restful风格的web开发中,根据不同的请求方法使用相应的控制器处理逻辑成为核心需求,下面就看看如何在Spring MVC中识别不同的请求方法。 请...

2329
来自专栏Java架构

阿里面试答案——Spring框架

2123
来自专栏芋道源码1024

注册中心 Eureka 源码解析 —— 调试环境搭建

本文主要基于 Eureka 1.8.X 版本 1. 依赖工具 2. 源码拉取 3. Eureka-Server 启动 3.1 MockRemoteEurekaS...

3676
来自专栏一个会写诗的程序员的博客

Spring Boot 使用 Spring Session 集成 Redis 实现Session共享Spring Boot 使用 Spring Session 集成 Redis 实现Session共享

通常在web开发中,Session 会话管理是很重要的一部分,用于存储与用户相关的一些数据。在Java Web 系统中的 Session一般由 Tomcat 容...

8335
来自专栏崔庆才的专栏

是时候抛弃print了,开始体验下logging的强大吧!

2052

扫码关注云+社区

领取腾讯云代金券