前言:

要想用好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同步锁是如何实现的相关推荐

  1. Synchronized同步锁

    导致线程安全的问题在于,存在多个线程2同时操作一个共享资源,需要解决这个问题,就需要保证对共享资源访问的独占性,因此人们在Java中提供了synchronized关键字,我们称之为同步锁,它可以保证在 ...

  2. Java多线程系列(六):深入详解Synchronized同步锁的底层实现

    谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如: synchronized的底层实现原理 s ...

  3. java 同步锁_Java多线程:synchronized同步锁的使用和实现原理

    作用和用法 在多线程对共享资源进行并发访问方面,JDK提供了synchronized关键字来进行线程同步,实现多线程并发访问的线程安全.synchronized的作用主要体现在三个方面:(1)确保线程 ...

  4. 一文搞懂Synchronized同步锁的作用范围

    文章目录 类锁 修饰静态方法 修饰代码块 实战演练 对象锁 修饰普通方法 修饰代码块 实战演练 分析   我们对一个方法 增加Synchronized关键字后,当多个线程访问该方法时,整个执行过程会变 ...

  5. java中synchronized同步锁实现生产者消费者模式

    synchronized介绍 一.基本概念 synchronized关键字是java里面用来在多线程环境下保证线程安全的同步锁:java里面有对象锁和类锁,对象锁是用在对象实例的方法上或者一个对象实例 ...

  6. 三、synchronized同步锁

    一.简介 在Java多线程中,我们要实现同步串行最早接触的就是synchronized关键字. 基本语法如下: synchronized(锁) {// 代码块 } sychronized关键字的锁主要 ...

  7. synchronized同步锁原理详解

    Java对象头 JVM中对象头的结构有以下两种(以32位JVM为例): 普通对象的对象头结构 数组对象的对象头结构 其中Mark Word结构 64位虚拟机 Mark Word的结构 Mark Wor ...

  8. synchronized同步锁的三种方式

    不多说,直接上代码 import java.util.ArrayList; import java.util.Collections; import java.util.List; import ja ...

  9. Java中String做为synchronized同步锁

    synchronized (("" + userId).intern()) {// TODO:something} JVM内存区域里面有一块常量池,关于常量池的分配: JDK6的版 ...

最新文章

  1. 什么是布隆过滤器?如何解决高并发缓存穿透问题?
  2. 移动超级sim卡 无法下载卡_中国移动发布超级SIM卡:全变了
  3. linxu其他用户登录mysql_Linux系统的MySQL用户如何开启远程登录权限
  4. SHELL编程中如果路径名遇到括号
  5. 李洋疯狂C语言之求素数的方法
  6. 带绿色箭头指示的滑动门DIV效果
  7. java抢购防止多次请求_springboot项目中接口防止恶意请求多次
  8. matlab的函数要写在哪,matlab函数库在哪
  9. Final Project Proposal ——陈稳霖
  10. python半径为2.11的圆球的体积_python 学习笔记 11 -- 使用参数使你的程序变得更性感...
  11. 计算机网络(第七版)谢希仁编著 前五章课后答案计算题详解
  12. 视频教程-ThreeJS视频教程-JavaScript
  13. 大众点评文字反爬破解
  14. DHCP Relay 配置教程
  15. 2022全新Java学习路线图动力节点(四)Javaweb前端与后端
  16. 微信企业号开发,给用户推送信息
  17. hive存储处理器(StorageHandlers)以及存储格式以及hive与hbase整合
  18. STM32F10xxx20xxx21xxxL1xxxx Cortex-M3程序设计手册 阅读笔记二(2):Cortex-M3处理器内存模型
  19. python读取灰度图_Python读取MRI并显示为灰度图像实例代码
  20. MySQL索引下推需要了解下

热门文章

  1. MessageBox提示框自动关闭
  2. python(matplotlib)绘制直方图及阶梯图
  3. php圆的周长_php面向对象编程练习:计算矩形、三角形、圆形的周长和面积
  4. 区块链100讲:Truffle——一个更简单的部署智能合约的方法
  5. 蓝牙技术|了解蓝牙LE Audio的Auracast广播音频
  6. 有三类人永远做不了程序员
  7. 建模配置 | Revit建模到底需要什么配置
  8. 互联网日报 | 5月2日 星期日 | 五一档总票房破5亿;中国联通在香港正式推出5G服务;欧盟首次对苹果发起反垄断诉讼
  9. windows10下Docker部署Kurento
  10. 发布订阅模式vs观察者模式