Synchronized同步锁是如何实现的
前言:
要想用好Synchronized锁,首先得了解清楚其实现同步锁的原理
原理解析
首先,看下其修饰代码块时:
//关键字在代码块上,锁为括号里面的对象
public void method2(){Object o = new Object();synchronized(0){//code}
}
Synchronized在修饰同步代码块时,是由monitorenter和monitorexit指令来实现同步的。进入monitorenter指令后,线程将持有Monitor对象,退出monitorenter指令后,线程将释放该Monitor对象。
// access flags 0x1public method2()VTRYCATCHBLOCK L0 L1 L2 nullTRYCATCHBLOCK L2 L3 L2 nullL4LINENUMBER 16 L4NEW java/lang/ObjectDUPINVOKESPECIAL java/lang/Object.<init> ()VASTORE 1L5LINENUMBER 17 L5ALOAD 1DUPASTORE 2MONITORENTERL0LINENUMBER 19 L0ALOAD 2MONITOREXITL1GOTO L6L2FRAME FULL [com/dragon/learn/leean1/SynchronizedTest java/lang/Object java/lang/Object] [java/lang/Throwable]ASTORE 3ALOAD 2MONITOREXITL3ALOAD 3ATHROWL6LINENUMBER 20 L6FRAME CHOP 1RETURNL7LOCALVARIABLE this Lcom/dragon/learn/leean1/SynchronizedTest; L4 L7 0LOCALVARIABLE o Ljava/lang/Object; L5 L7 1MAXSTACK = 2MAXLOCALS = 4
}
修饰方法
当Synchronized修饰同步方法时,并没有monitorenter和monitorexit指令,而是出现了一个ACC_SYNCHRNIZED标识。
Monitor
JVM中的同步是基于进入和退出(Monitor)对象的。每个对象实例都会有一个Monitor,Monitor可以和对象一起创建、销毁。Monitor是由ObjectMonitor实现,而ObjectMonitor是由C++的ObjectMonitor.hpp文件实现,如下所示:
ObjectMonitor() {_header = NULL;_count = 0; // 记录个数_waiters = 0,_recursions = 0;_object = NULL;_owner = NULL;_WaitSet = NULL; // 处于 wait 状态的线程,会被加入到 _WaitSet_WaitSetLock = 0 ;_Responsible = NULL ;_succ = NULL ;_cxq = NULL ;FreeNext = NULL ;_EntryList = NULL ; // 处于等待锁 block 状态的线程,会被加入到该列表_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ;
}
当多个线程同时访问同一个代码块时,首先会将这线程放入到ContenionList和EntryList中。之后线程通过操作系统的Mutes Lock来获取锁。如果获取到了,则执行相应的代码。如果没有获取到,则重新进入ContenionList。如果调用了wait方法,则会进入WaitSet。当其他线程调用notify方法时会唤醒并重新进入EntryList.
锁升级优化
Java对象头
Java对象有对象头、实例数据、填充数据三部分组成。其中对象头由标记字段,类型指针,数组长度三部分组成。
偏向锁
偏向锁主要是用来优化同一个线程多次申请同一个锁的竞争。偏向锁的作用是当一个线程再次访问同步代码或方法时,只需在对象头上判断线程的偏向锁的线程ID是否为当前线程。如果是的话,则不用再次进入Monitor去竞争对象了。
如果有其它线程竞争该资源时,则该偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程。同时检查该线程是否还在执行该方法,如果是,则升级锁,反之,则其它线程抢占。
因此,在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生stop the word后,开启偏向锁无疑会带来更大的性能开销,这时我们可以通过添加JVM参数关闭偏向锁来调优系统性能,示例代码如下:
偏向锁设置方法
-XX:-UseBiasedLocking //关闭偏向锁(默认打开)-XX:+UseHeavyMonitors //设置重量级锁
轻量级锁
当另外有一个线程获取锁时,发现该锁已经是偏向锁了,那么就会通过CAS的方式去获取锁,如果获取成功,那么直接替换标记字段的类型线程ID为当前线程。如果获取失败,那么就会撤偏向锁,转为轻量级锁。
轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争。
自旋锁和重量级锁
轻量级锁CAS获取锁失败, 默认会通过自旋的方式来获取锁。自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁,锁标志位改为 10。在这个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在 _WaitSet 队列中。
锁消除与锁粗化
JIT编译器在动态编译同步代码块的时候,会通过逃逸分析的技术。如果确定这个代码块只会被一个线程访问,那么就会进行锁消除。
锁粗化同理,就是在 JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。
减小锁粒度
这个主要是在代码层面进行优化。
例如,JDK8之前的ConcurrentHashMap,通过分段的机制来控制。
总结
1、检查Mark Word里面是不是当前线程ID,如果是,表示当前线程处于偏向锁。
2、如果不是,则使用CAS将当前线程ID替换成Mark Word,如果成功则表示当前线程获得偏向锁,设置偏向标志为1,如果失败,则说明发生了竞争,撤销偏向锁,升级为轻量级锁
3、当前线程使用CAS将对象头的mark Word锁标记位替换为锁记录指针,如果成功,当前线程获得锁。如果失败表示其它线程竞争锁,当前线程尝试通过自旋获取锁
4、如果自旋成功则依然处于轻量级状态,如果自旋失败升级为重量级锁
Synchronized同步锁是如何实现的相关推荐
- Synchronized同步锁
导致线程安全的问题在于,存在多个线程2同时操作一个共享资源,需要解决这个问题,就需要保证对共享资源访问的独占性,因此人们在Java中提供了synchronized关键字,我们称之为同步锁,它可以保证在 ...
- Java多线程系列(六):深入详解Synchronized同步锁的底层实现
谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如: synchronized的底层实现原理 s ...
- java 同步锁_Java多线程:synchronized同步锁的使用和实现原理
作用和用法 在多线程对共享资源进行并发访问方面,JDK提供了synchronized关键字来进行线程同步,实现多线程并发访问的线程安全.synchronized的作用主要体现在三个方面:(1)确保线程 ...
- 一文搞懂Synchronized同步锁的作用范围
文章目录 类锁 修饰静态方法 修饰代码块 实战演练 对象锁 修饰普通方法 修饰代码块 实战演练 分析 我们对一个方法 增加Synchronized关键字后,当多个线程访问该方法时,整个执行过程会变 ...
- java中synchronized同步锁实现生产者消费者模式
synchronized介绍 一.基本概念 synchronized关键字是java里面用来在多线程环境下保证线程安全的同步锁:java里面有对象锁和类锁,对象锁是用在对象实例的方法上或者一个对象实例 ...
- 三、synchronized同步锁
一.简介 在Java多线程中,我们要实现同步串行最早接触的就是synchronized关键字. 基本语法如下: synchronized(锁) {// 代码块 } sychronized关键字的锁主要 ...
- synchronized同步锁原理详解
Java对象头 JVM中对象头的结构有以下两种(以32位JVM为例): 普通对象的对象头结构 数组对象的对象头结构 其中Mark Word结构 64位虚拟机 Mark Word的结构 Mark Wor ...
- synchronized同步锁的三种方式
不多说,直接上代码 import java.util.ArrayList; import java.util.Collections; import java.util.List; import ja ...
- Java中String做为synchronized同步锁
synchronized (("" + userId).intern()) {// TODO:something} JVM内存区域里面有一块常量池,关于常量池的分配: JDK6的版 ...
最新文章
- 什么是布隆过滤器?如何解决高并发缓存穿透问题?
- 移动超级sim卡 无法下载卡_中国移动发布超级SIM卡:全变了
- linxu其他用户登录mysql_Linux系统的MySQL用户如何开启远程登录权限
- SHELL编程中如果路径名遇到括号
- 李洋疯狂C语言之求素数的方法
- 带绿色箭头指示的滑动门DIV效果
- java抢购防止多次请求_springboot项目中接口防止恶意请求多次
- matlab的函数要写在哪,matlab函数库在哪
- Final Project Proposal ——陈稳霖
- python半径为2.11的圆球的体积_python 学习笔记 11 -- 使用参数使你的程序变得更性感...
- 计算机网络(第七版)谢希仁编著 前五章课后答案计算题详解
- 视频教程-ThreeJS视频教程-JavaScript
- 大众点评文字反爬破解
- DHCP Relay 配置教程
- 2022全新Java学习路线图动力节点(四)Javaweb前端与后端
- 微信企业号开发,给用户推送信息
- hive存储处理器(StorageHandlers)以及存储格式以及hive与hbase整合
- STM32F10xxx20xxx21xxxL1xxxx Cortex-M3程序设计手册 阅读笔记二(2):Cortex-M3处理器内存模型
- python读取灰度图_Python读取MRI并显示为灰度图像实例代码
- MySQL索引下推需要了解下
热门文章
- MessageBox提示框自动关闭
- python(matplotlib)绘制直方图及阶梯图
- php圆的周长_php面向对象编程练习:计算矩形、三角形、圆形的周长和面积
- 区块链100讲:Truffle——一个更简单的部署智能合约的方法
- 蓝牙技术|了解蓝牙LE Audio的Auracast广播音频
- 有三类人永远做不了程序员
- 建模配置 | Revit建模到底需要什么配置
- 互联网日报 | 5月2日 星期日 | 五一档总票房破5亿;中国联通在香港正式推出5G服务;欧盟首次对苹果发起反垄断诉讼
- windows10下Docker部署Kurento
- 发布订阅模式vs观察者模式