我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java synchronized 锁机制原理详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java synchronized 锁机制原理详解

前言:

线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。

 1、synchronized 的作用:

synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,从而保证线程安全。synchronized 可以保证线程的可见性,synchronized 属于隐式锁,锁的持有与释放都是隐式的,我们无需干预。synchronized最主要的三种应用方式:

  • 修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块:指定加锁对象,进入同步代码库前要获得给定对象的锁

2、synchronized 底层语义原理:

synchronized 锁机制在 Java 虚拟机中的同步是基于进入和退出监视器锁对象 monitor 实现的(无论是显示同步还是隐式同步都是如此),每个对象的对象头都关联着一个 monitor 对象,当一个 monitor 被某个线程持有后,它便处于锁定状态。在 HotSpot 虚拟机中,monitor 是由 ObjectMonitor 实现的,每个等待锁的线程都会被封装成 ObjectWaiter 对象,ObjectMonitor 中有两个集合,WaitSet 和 _EntryList,用来保存 ObjectWaiter 对象列表 ,owner 区域指向持有 ObjectMonitor 对象的线程。当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合尝试获取 moniter,当线程获取到对象的 monitor 后进入 _Owner 区域并把 _owner 变量设置为当前线程,同时 monitor 中的计数器 count 加1;若线程调用 wait() 方法,将释放当前持有的 monitor,count自减1,owner 变量恢复为 null,同时该线程进入 _WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放 monitor 并复位变量的值,以便其他线程获取 monitor。如下图所示:

3、 synchronized 的显式同步与隐式同步:

synchronized 分为显式同步(同步代码块)和隐式同步(同步方法),显式同步指的是有明确的 monitorenter 和 monitorexit 指令,而隐式同步并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。

3.1、synchronized 代码块底层原理:

synchronized 同步语句块的实现是显式同步的,通过 monitorenter 和 monitorexit 指令实现,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置,当执行 monitorenter 指令时,当前线程将尝试获取 objectref(即对象锁)所对应的 monitor 的持有权:

  • 当对象锁的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。
  • 如果当前线程已经拥有对象锁的 monitor 的持有权,那它可以重入这个 monitor,重入时计数器的值也会加1。
  • 若其他线程已经拥有对象锁的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit 指令被执行,执行线程将释放 monitor 并设置计数器值为0,其他线程将有机会持有 monitor。

编译器会确保无论方法通过何种方式完成,无论是正常结束还是异常结束,代码中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令。为了保证在方法异常完成时,monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器可处理所有的异常,它的目的就是用来执行 monitorexit 指令。

3.2、synchronized 方法底层原理:

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

 如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的 monitor 将在异常抛到同步方法之外时自动释放。

 4、JVM 对 synchronized 锁的优化:

在早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁 monitor 是依赖于操作系统的 Mutex 互斥量来实现的,操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。在 JDK6 之后,synchronized 在 JVM 层面做了优化,减少锁的获取和释放所带来的性能消耗,主要优化方向有以下几点:

4.1、锁升级:偏向锁->轻量级锁->自旋锁->重量级锁

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,只能从低到高升级,不会出现锁的降级。重量级锁基于从操作系统的互斥量实现的,而偏向锁与轻量级锁不同,他们是通过 CAS 并配合 Mark Word 一起实现的。

4.1.1、synchronized 的 Mark word 标志位:

synchronized 使用的锁对象是存储在 Java 对象头里的,那么 Java 对象头是什么呢?对象实例分为:

  • 对象头
    • Mark Word
    • 指向类的指针
    • 数组长度
  • 实例数据
  • 对齐填充

其中,Mark Word 记录了对象的 hashcode、分代年龄、锁标记位相关的信息,由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到 JVM 的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,在 32位 JVM 中的长度是 32 位,具体信息如下图所示:

4.1.2、锁升级过程:

(1)偏向锁:如果一个线程获得了锁,那么进入偏向模式,当这个线程再次请求锁的时候,只需去对象头的 Mark Word 中判断偏向线程ID是否指向它自己,无需再进入 monitor 中去竞争对象,这样就省去了大量锁申请的操作,适用于连续多次都是同一个线程申请相同的锁的场景。偏向锁只有初始化的时候需要一次 CAS 操作,但如果出现其他线程竞争锁资源,那么偏向锁就会被撤销,并升级为轻量级锁。

(2)轻量级锁:不需要申请互斥量,允许短时间内的锁竞争,每次申请、释放锁都至少需要一次 CAS,适用于多个线程交替执行同步代码块的场景

(3)自旋锁:自旋锁假设在不久将来,当前的线程可以获得锁,因此在轻量级锁升级成为重量级锁之前,虚拟机会让当前想要获取锁的线程做几个空循环,在经过若干次循环后,如果得到锁,就顺利进入临界区,如果还不能获得锁,那就会将线程在操作系统层面挂起。

这种方式确实可以提升效率的,但是当线程越来越多竞争很激烈时,占用 CPU 的时间变长会导致性能急剧下降,因此 JVM 对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。

(4)自适应自旋锁:自适应自旋解决的是 “锁竞争时间不确定” 的问题,自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

  • 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。
  • 相反的,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。

但自旋锁带来的副作用就是不公平的锁机制:处于阻塞状态的线程,并没有办法立刻竞争被释放的锁。然而,处于自旋状态的线程,则很有可能优先获得这把锁。

(5)重量级锁:适用于多个线程同时执行同步代码块的场景,且锁竞争时间长。在这个状态下,未抢到锁的线程都会进入到 Monitor 中并阻塞在 _WaitSet 集合中。

4.2、锁消除:

消除锁属于编译器对锁的优化,JIT 编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译)会使用逃逸分析技术,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间。

4.3、锁粗化:

JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。

5、偏向锁的废除:

 在 JDK6 中引入的偏向锁能够减少竞争锁定的开销,使得 JVM 的性能得到了显著改善,但是 JDK15 却将决定将偏向锁禁用,并在以后删除它,这是为什么呢?主要有以下几个原因:

  • 为了支持偏向锁使得代码复杂度大幅度提升,并且对 HotSpot 的其他组件产生了影响,这种复杂性已成为理解代码的障碍,也阻碍了对同步系统进行重构
  • 在更高的 JDK 版本中针对多线程场景推出了性能更高的并发数据结构,所以过去看到的性能提升,在现在看来已经不那么明显了。
  • 围绕线程池队列和工作线程构建的应用程序,性能通常在禁用偏向锁的情况下变得更好。

锁升级过程详细解析推荐阅读:https://www.jb51.net/article/186708.htm

参考文章://www.jb51.net/article/221033.htm

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java synchronized 锁机制原理详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java关键字synchronized原理与锁的状态详解

在Java当中synchronized关键字通常是用来标记一个方法或者代码块。本文将通过示例为大家详细介绍一下Synchronized的各种使用方法,需要的可以参考一下
2022-11-13

AQS加锁机制Synchronized相似点详解

这篇文章主要为大家介绍了AQS加锁机制Synchronized相似点详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Java Synchronized锁的使用详解

在多线程并发问题中,常用Synchronized锁解决问题。本篇文章主要介绍了并发编程中Synchronized锁的用法知识记录,感兴趣的小伙伴可以了解一下
2022-11-13

浅谈Java的Synchronized锁原理和优化

这篇文章主要介绍了Java的Synchronized锁原理和优化,synchronized的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果,需要的朋友可以参考下
2023-05-20

Java 多线程同步 锁机制与synchronized深入解析

从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间
2022-11-15

怎么用Java实现synchronized锁同步机制

这期内容当中小编将会给大家带来有关怎么用Java实现synchronized锁同步机制,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。synchronized 实现原理synchronized 是通过进入和
2023-06-25

Java中的synchronized锁膨胀机制怎么实现

这篇文章主要讲解了“Java中的synchronized锁膨胀机制怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中的synchronized锁膨胀机制怎么实现”吧!synch
2023-06-30

Java synchronized偏向锁的核心原理是什么

本篇内容主要讲解“Java synchronized偏向锁的核心原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java synchronized偏向锁的核心原理是什么”吧!1. 偏向锁
2023-06-29

深入理解java内置锁(synchronized)和显式锁(ReentrantLock)

synchronized 和 Reentrantlock多线程编程中,当代码需要同步时我们会用到锁。Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式。显式锁是JDK1.5引入的,这两种
2023-05-30

java中注解机制及其原理的详解

java中注解机制及其原理的详解什么是注解注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录