JDK1.6 对 synchronized 的锁优化
1. 背景
在 JDK 1.6 中对锁的实现引入了大量的优化。
目的
减少锁操作的开销。
2. 锁优化
在看下面的内容之间,希望大家对 Mark Word 有个大体的理解。Java 中一个对象在堆中的内存结构是这样的:
Mark Word 是这样的:
2.1 适应性自旋锁
自旋锁的思想:
让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。
自旋锁的缺点:
需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。
若锁被其他线程长时间占用,会带来许多性能上的开销。所以自旋的次数不再固定。由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
如果共享数据的锁定状态持续时间较短,切换线程不值得(会有上下文切换),可以利用自旋锁尝试一定的次数。
2.2 锁消除
JIT 编译时,会去除不可能存在竞争的锁。通过 JIT 的逃逸分析来消除一些没有在当前同步块以外被其他线程共享的数据的锁的保护,通过逃逸分析在 TLAB 来分配对象,这样就不存在共享数据带来的线程安全问题。
2.3 锁粗化
减少不必要的紧连在一起的 lock,unlock 操作,将多个连续的锁扩展成一个范围更大的锁。
2.4 偏向锁(重入锁)
为了在无线程竞争的情况下避免在锁获取过程中执行不必要的 CAS 原子指令,因为 CAS 原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟(因为 CAS 的底层是利用 LOCK 指令 + cmpxchg 汇编指令来保证原子性的,LOCK 指令会锁总线,其他 CPU 的内存操作将会被阻塞,因为 CPU 架构如果是 CMU 的话,控制信号、数据信号等是通过共享总线传到内存控制器中)。减少同一线程获取锁的代价,省去了大量有关锁申请的操作。
核心思想
如果一个线程获得了锁, 那么锁就进入偏向模式,此时 Mark Word 的结构也变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查 Mark Word 的锁标记位为偏向锁以及当前线程 Id 等于 Mark Word 的 ThreadId 即可,这样就省去了大量有关锁申请的操作。
2.5 轻量级锁
这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁(重量级锁的底层就是这样实现的),只需要依靠一条 CAS 原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行 CAS 指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒。
2.5.1 加锁的过程
主要分为 3 步:
1、在线程进入同步块的时候,如果同步对象状态为无锁状态(锁标志为 01),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用来存储锁对象目前的 Mark Word 的拷贝。拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record 里的 owner 指针指向锁对象的 Mark Word。如果更新成功,则执行 2,否则执行 3。
2、如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且锁对象的 Mark Word 中的锁标志位设置为 "00",即表示此对象处于轻量级锁定状态,这时候虚拟机线程栈与堆中锁对象的对象头的状态如图所示。
3、如果这个更新操作失败了,虚拟机首先会检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重要量级锁,锁标志的状态值变为 "10",Mark Word 中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁。自旋失败后膨胀为重量级锁,被阻塞。
2.5.2 解锁的过程
因为虚拟机线程栈帧中的 Displaced Mark Word 是最初的无锁状态时的数据结构,所以用它来替换对象头中的 Mark Word 就可以释放锁。如果锁已经膨胀为重量级,此时是不可以被替换的,所以替换失败,唤醒被挂起的线程。
3. 心得
锁膨胀的过程
其实就是对象头中的 Mark Word 数据结构改变的过程。
4. 三种锁的对比
4.1 偏向锁
只需要判断 Mark Word 中的一些值是否正确就行。
只有一个线程访问同步块时,使用偏向锁。
4.2 轻量级锁
需要执行 CAS 操作自旋来获取锁。
如果执行同步块的时间比较少,那么多个线程之间执行使用轻量级锁交替执行。
4.3 重量级锁
会发生上下文切换,CPU 状态从用户态转换为内核态执行操作系统提供的互斥锁,所以系统开销比较大,响应时间也比较缓慢。
如果执行同步块的时间比较长,那么多个线程之间刚开始使用轻量级锁,后面膨胀为重量级锁。(因为执行同步块的时间长,线程 CAS 自旋获得轻量级锁失败后就会锁膨胀)
5. 总结
参考书籍:《深入理解 Java 虚拟机》
搜索微信公众号:Java知其所以然,可免费领取某课、Java 后端面经等资源,还有统一环境(教你怎么配置一套开发环境)视频领取。
JDK1.6 对 synchronized 的锁优化的更多相关文章
- Java并发编程:synchronized和锁优化
1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...
- synchronized底层实现原理及锁优化
一.概述 1.synchronized作用 原子性:synchronized保证语句块内操作是原子的 可见性:synchronized保证可见性(通过"在执行unlock之前,必须先把此变量 ...
- 深入理解java虚拟机-第13章-线程安全与锁优化
第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...
- 深入介绍Java中的锁[原理、锁优化、CAS、AQS]
1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...
- Java中的锁[原理、锁优化、CAS、AQS]
1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...
- 深入浅出JVM的锁优化案例
锁优化 适应性自旋(Adaptive Spinning) 线程阻塞的时候,让等待的线程不放弃cpu执行时间,而是执行一个自旋(一般是空循环),这叫做自旋锁. 自旋等待本身虽然避免了线程切换的开销,但它 ...
- Java中的锁原理、锁优化、CAS、AQS详解!
阅读本文大概需要 2.8 分钟. 来源:jianshu.com/p/e674ee68fd3f 一.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 二.锁实现的基本原理 2.1.v ...
- Java 中的锁原理、锁优化、CAS、AQS 详解!(转)
1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...
- 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现
一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...
随机推荐
- C语言调用VIX_API开关虚拟机
#include <stdio.h> #include <stdlib.h> #include "vix.h" #define USE_WORKSTATIO ...
- Python在office开发中的应用
Python with Excel 有几个很好的Python模块能够方便地操作Excel的数据,包括读与写,不要求本地安装Excel.例如pandas, openpyxl, xlrd, xlutils ...
- Cobbler-自动化部署神器
Cobbler-自动化部署神器 前言: 网络安装服务器套件 Cobbler(补鞋匠)从前,我们一直在做装机民工这份很有前途的职业.自打若干年前 Red Hat 推出了 Kickstart,此后我们顿觉 ...
- Java类什么情况下被初始化?
1.创建类的实例(new 的方式).访问某个类或接口的静态变量,或者对该静态变量赋值,调用类的静态方法 2.反射的方式 3.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需先触发其父类的初始 ...
- 逆向MFC程序
目录 @ 1 MFC执行流程 1.1 环境支持 1.2 分析 1.3 实践探索 1.3.1 创建一个MFC程序 1.3.2 下关键断点并调试 1.4 转向MFC库源文件中观测 2 逆向 2.1 特征码 ...
- HTML5-新增语义化结构标签
总结目录结构: 1.简洁的DOCTYPE声明 2.新的布局结构标签 header,article,section,aside,footer 3.新的其它常用标签: nav,hgroup,figure, ...
- 机器学习经典分类算法 —— k-近邻算法(附python实现代码及数据集)
目录 工作原理 python实现 算法实战 约会对象好感度预测 故事背景 准备数据:从文本文件中解析数据 分析数据:使用Matplotlib创建散点图 准备数据:归一化数值 测试算法:作为完整程序验证 ...
- 03-k8s认证
目录 k8s认证 客户端 ---> API Server 外部访问 pod 客户端 RBCA k8s 用户类型 dashboard 的认证登录 k8s认证 主要使用 RBAC授权检查机制 认证: ...
- 【iOS】iOS Error Domain=NSCocoaErrorDomain Code=3840 "未能完成操作。(“Cocoa”错误 3840。)"
昨天遇到的这个问题,详细信息: ----->类和方法__25+[Manager noticeRequest:]_block_invoke399----->错误信息Error Domain= ...
- QRCode生成二维码,jq QRCode生成二维码,QRCode生成电子名片
[QRCode官网]http://phpqrcode.sourceforge.net/ PHP QRCode生成二维码 官网下载QRCode源码包,引入源码包中的 qrlib.php . <?p ...