synchronized原理

栏目: Java · 发布时间: 5年前

内容简介:前边的文章中已经介绍了在《深入理解Java虚拟机》一书中,介绍了因此,

前边的文章中已经介绍了 synchronized 的基本用法 ,我们也知道了 synchronized 使用锁,来保证被锁定了代码同一时间只能有一个线程执行;那么 synchronized 关键字的实现原理是怎样的呢?

在《深入理解 Java 虚拟机》一书中,介绍了 HotSpot 虚拟机中,对象的内存布局分为三个区域:对象头(Header)、实例数据(Instance Data)和对齐数据(Padding)。而对象头又分为两个部分“Mark Word”和类型指针,其中“Mark Word”包含了线程持有的锁。

因此, synchronized 锁,也是保存在对象头中。JVM基于进入和退出 Monitor 对象来实现 synchronized 方法和代码块的同步,对于方法和代码块的实现细节又有不同:

  • 代码块,使用 monitorentermonitorexit 指令来实现; monitorenter 指令编译后,插入到同步代码块开始的位置, monitorexit 指令插入到方法同步代码块结束位置和异常处,JVM保证每个 monitorenter 必须有一个 monitorexit 指令与之对应。线程执行到 monitorenter 指令处时,会尝试获取对象对应的 Monitor 对象的所有权 (任何一个对象都有一个 Monitor 对象预制对应,当一个 Monitor 被持有后,它将处于锁定状态) 。

  • 方法:在《深入理解Java虚拟机》同步指令一节中,关于方法级的同步描述如下:

    方法级的同步是隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程获取了管程,其他线程就无法获取管程。

2. Java对象头

上边的介绍中,我们知道 synchronized 锁存在在Java对象的对象头中,那么,什么是Java的对象头呢?

HotSpot虚拟机中,对象在内存中存储的布局可以分为3个部分:对象头,实例数据和对齐填充。其中,实例数据部分是对象真正存储的有效信息,对齐填充并不是必然存在,虚拟机要求对象起始地址必须是8字节的整数倍,如果对象实例数据部分没有达到8字节的整数倍,需要对齐填充区来补全。

对象头包含两部分信息:Mark Word和类型指针。虚拟机通过类型指针确定这个对象是哪个类的实例。

Mark Word中主要包含了:哈希码(HashCode)、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等,这些信息在32位和64位虚拟机中,分别占32bit和64bit。如果对象是数组类型,则需要多一个字宽存储数组的长度。32位JVM的Mark Word默认存储结构如下表所示:

锁状态 25bit 4bit 1bit是否偏向锁 2bit锁标志位
无锁状态 对象的 hashCode 对象分代年龄 0 01

Mark Word中存储的数据会随着锁标志位的变化而变化:

锁状态 25bit 4bit 1bit 2bit
23bit 2bit 是否是偏向锁 锁标志位
轻量级锁 指向栈中锁记录的指针 00
重量级锁 指向互斥量(重量级锁)记录的指针 10
GC 标记 11
偏向锁 线程ID Epoch 对象分代年龄 1 01

64为虚拟机中,Mark Work存储结构如下表所示:

锁状态 25bit 31bit 1bit(cms_free) 4bit 分代年龄 1bit 偏向锁 2bit 锁标志位
无锁 unused hashCode 0 01
偏向锁 ThreadID(54bit) Epoch(2bit) 1 01

- 以上表格内容摘自《Java并发编程艺术》——方腾飞

3. 锁优化

上边介绍了 synchronized 锁,主要通过获取Monitor来获取锁,而获取和释放Monitor的成本会非常高,因为,线程之间需要进行用户态到内核态的切换。JDK 1.6为了减少获取锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,JDK 1.6中锁一共有4中状态依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。另外,还使用了很多的锁优化技术,如适应性自旋锁、锁消除、锁粗化等。

3.1 自旋锁和自适应自旋锁

首先,什么自旋锁?顾名思义就是一个线程不停的自旋(循环),以达到代码块只有一个线程执行的同步效果。那么为什么会引入自旋锁呢?因为在许多应用中,共享数据的锁定状态只会持续很短的时间,短时间内挂起和恢复线程是浪费的,因此,为了让线程保持运行,但是不去争夺同步资源,引入了自旋锁,即让线程执行一个忙循环,当持有锁的线程释放锁后,该线程再去获取锁。避免线程挂起和恢复消耗资源。

自旋锁在JDK 1.4.2 中已经引入了,但是默认是关闭的,可以使用 -XX:+UseSpinning 参数开启,在JDK 1.6 中默认开启。

上边介绍了引入自旋锁的需求,锁持续的时间很短,这种情况下,让线程自旋等待相比挂起和恢复,似乎是更高效的方式;但是,如果锁长时间未释放,那么线程将一直自旋下去,这样会张勇太多的处理器时间,白白消耗处理器资源做无用功,带来性能上的浪费。因此,自旋锁等待的时间必须要有一定的限制,超过了规定次数,还没有获得锁,线程就会挂起。自旋次数默认是10次,可以使用 -XX:PreBlockSpin 参数来修改。

上边的自旋锁已经可以自旋指定的次数了,这样相比原来不停的自旋或者直接挂起线程已经高效很多了;可是如果我们自旋10次之后,刚挂起线程,就获得了锁,有需要唤醒线程,我们的处理器肯定在想你为什么不多自旋一会,我们的自旋锁是否能够更加智能呢?

JVM 的开发者没有让我们失望,在JDK 1.6 中引入了自适应的自旋锁,自适应就是说自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。自适应自旋锁认为,如果同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在执行,那么虚拟机就会任务这次自旋锁很可能会再次成功,将会等待更长的时间;如果某个锁,很少更改获得,那么后边的线程将忽略自旋的过程,直接挂起,避免处理器资源的浪费。

3.2 锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。

那么 JVM 虚拟机是如何检测锁不可能存在数据竞争呢?另外,如果不存在数据竞争我们再写代码的时候不加锁不久可以了,虚拟机为什么还要做这个检测呢?

虚拟机使用逃逸分析技术,来检测锁是否存在竞争;虽然我们再写代码时,可能不会加锁,但是javac编译器会对我们的程序进行自动优化,因此可能就会引入部分我们自己都不知道的锁,所以,虚拟机的锁消除还是有必要的,而且也更好的优化了程序的性能。

3.3 锁粗化

锁粗化就是将多个连续的锁,连接在一起,扩展成一个范围更大的锁。但是,在刚学习多线程时,总是推荐我们同步代码块的范围要尽可能小,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快得到锁。

大部分情况下,我们遵循以上的原则是正确的,但是如果一系列连续的操作都对同一个对象加锁(例如:频繁调用一些同步方法,StringBuffer.append()等),程序将会频繁的进行加锁解锁过程,影响效率。因此,虚拟机如果检测到这种对同一对象加锁的连续操作,将会把加锁范围扩展到整个操作序列的外部。

3.4 偏向锁

大多数情况下,锁总是由一个线程多次获得,这种情况下,为了降低线程获取锁的代价,引入了偏向锁。线程每次进入同步代码块,会在对象头中记录线程ID,一个该线程如果再次进入和退出同步代码块时,检测到对象头中存储的指向当前线程的偏向锁,则不需要使用CAS加锁和解锁;如果检测失败,则再检测一下对象头中的偏向锁标识是否设置为1,即当前是偏向锁:如果不是偏向锁,使用CAS竞争锁;如果是,使用CAS将对象头的偏向锁指向当前线程。

  • 释放偏向锁 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤如下:
    1. 暂停拥有偏向锁的线程,判断锁对象是否还处于被锁定状态;
    2. 撤销偏向锁,恢复到无锁状态(01)或者轻量级锁的状态;

偏向锁获取和释放的流程大致如下图:(图片来源并发编程网-ifeve.com)

synchronized原理

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

RESTful Web Services Cookbook

RESTful Web Services Cookbook

Subbu Allamaraju / Yahoo Press / 2010-3-11 / USD 39.99

While the REST design philosophy has captured the imagination of web and enterprise developers alike, using this approach to develop real web services is no picnic. This cookbook includes more than 10......一起来看看 《RESTful Web Services Cookbook》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器