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

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

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

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

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

synchronized 实现原理

synchronized 是通过进入和退出 Monitor 对象实现锁机制,代码块通过一对 monitorenter/monitorexit 指令实现。在编译后,monitorenter 指令插入到同步代码块的开始位置,monitorexit 指令插入到方法结束和异常处,JVM 要保证 monitorenter 和 monitorexit 成对出现。任何对象都有一个 Monitor 与之关联,当且仅当一个 Monitor 被持有后,它将处于锁状态。

在执行 monitorenter 时,首先尝试获取对象的锁,如果对象没有被锁定或者当前线程持有锁,锁的计数器加 1;相应的,在执行 monitorexit 指令时,将锁的计数器减 1。当计数器减到 0 时,锁释放。如果在 monitorenter 获取锁失败,当前线程会被阻塞,直到对象锁被释放。

在 JDK6 之前,Monitor 的实现是依靠操作系统内部的互斥锁实现(一般使用的是 Mutex Lock 实现),线程阻塞会进行用户态和内核态的切换,所以同步操作是一个无差别的重量级锁。

后来,JDK 对 synchronized 进行升级,为了避免线程阻塞时在用户态与内核态之间切换线程,会在操作系统阻塞线程前,加入自旋操作。然后还实现 3 种不同的 Monitor:偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)、重量级锁。在 JDK6 之后,synchronized 的性能得到很大的提升,相比于 ReentrantLock 而言,性能并不差,只不过 ReentrantLock 使用起来更加灵活。

适应性自旋(Adaptive Spinning)

synchronized 对性能影响最大的是阻塞的实现,挂起线程和恢复线程都需要操作系统帮助完成,需要从用户态转到内核态,状态转换需要耗费很多 CPU 时间。

在我们大多数的应用中,共享数据的锁定状态只会持续很短的一段时间,为了这段时间挂起和回复线程消耗的时间不值得。而且,现在大多数的处理器都是多核处理器,如果让后一个线程再等一会,不释放 CPU,等前一个释放锁,后一个线程立马获取锁执行任务就行。这就是所谓的自旋,让线程执行一个忙循环,自己在原地转一会,每转一圈看看锁释放没有,释放了直接获取锁,没有释放就再转一圈。

自旋锁是在 JDK 1.4.2 引入(使用-XX:+UseSpinning参数打开),JDK 1.6 默认打开。自旋锁不能代替阻塞,因为自旋等待虽然避免了线程切换的开销,但是它要占用 CPU 时间,如果锁占用时间短,自旋等待效果挺好,反之,则是性能浪费。所以在 JDK 1.6 中引入了自适应自旋锁:如果同一个锁对象,自旋等待刚成功,且持有锁的线程正在运行,那本次自旋很有可能成功,会允许自旋等待持续时间长一些。反之,如果对于某个锁,自旋很少成功,那之后很有可能直接省略自旋过程,避免浪费 CPU 资源。

锁升级

Java 对象头

synchronized 用的锁存在于 Java 对象头里,对象头里的 Mark Word 里存储的数据会随标志位的变化而变化,变化如下:

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

Java 对象头 Mark Word

偏向锁(Biased Locking)

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引入偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID,以后该线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁,只需简单地测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换 ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。

偏向锁获取
  • 当锁对象第一次被线程获取时,对象头的标志位设为 01,偏向模式设为 1,表示进入偏向模式。

  • 测试线程 ID 是否指向当前线程,如果是,执行同步代码块,如果否,进入 3

  • 使用 CAS 操作把获得到的这个锁的线程 ID 记录在对象的 Mark Word 中。如果成功,执行同步代码块,如果失败,说明存在过其他线程持有锁对象的偏向锁,开始尝试当前线程获取偏向锁

  • 当到达全局安全点时(没有字节码正在执行),会暂停拥有偏向锁的线程,检查线程状态。如果线程已经结束,则将对象头设置成无锁状态(标志位为“01”),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁状态(标志位为“00”),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。

偏向锁释放

偏向锁的释放采用的是惰性释放机制:只有等到竞争出现,才释放偏向锁。释放过程就是上面说的第 4 步,这里不再赘述。

关闭偏向锁

偏斜锁并不适合所有应用场景,撤销操作(revoke)是比较重的行为,只有当存在较多不会真正竞争的同步块时,才能体现出明显改善。实践中对于偏斜锁的一直是有争议的,有人甚至认为,当你需要大量使用并发类库时,往往意味着你不需要偏斜锁。

所以如果你确定应用程序里的锁通常情况下处于竞争状态,可以通过 JVM 参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

轻量级锁(Lightweight Locking)

轻量级锁不是用来代替重量级锁的,它的初衷是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能损耗。

轻量级锁获取

如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如下图所示:

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

拷贝对象头中的 Mark Word 复制到锁记录(Lock Record)中。

拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock record 里的 owner 指针指向 object mark word。

如果成功,当前线程持有该对象锁,将对象头的 Mark Word 锁标志位设置为“00”,表示对象处于轻量级锁定状态,执行同步代码块。这时候线程堆栈与对象头的状态如下图所示:

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

如果更新失败,检查对象头的 Mark Word 是否指向当前线程的栈帧,如果是,说明当前线程拥有锁,直接执行同步代码块。

如果否,说明多个线程竞争锁,如果当前只有一个等待线程,通过自旋尝试获取锁。当自旋超过一定次数,或又来一个线程竞争锁,轻量级锁膨胀为重量级锁。重量级锁使除了拥有锁的线程以外的线程都阻塞,防止 CPU 空转,锁标志的状态值变为“10”,Mark Word 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

轻量级锁解锁
  • 轻量级锁解锁的时机是,当前线程同步块执行完毕。

  • 通过 CAS 操作尝试把线程中复制的 Displaced Mark Word 对象替换当前的 Mark Word。

  • 如果成功,整个同步过程完成

  • 如果失败,说明存在竞争,且锁膨胀为重量级锁。释放锁的同时,会唤醒被挂起的线程。

重量级锁

轻量级锁适应的场景是线程近乎交替执行同步块的情况,如果存在同一时间访问相同锁对象时(第一个线程持有锁,第二个线程自旋超过一定次数),轻量级锁会膨胀为重量级锁,Mark Word 的锁标记位更新为 10,Mark Word 指向互斥量(重量级锁)。

重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)。操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 JDK 1.6 之前,synchronized 重量级锁效率低的原因。

下图是偏向锁、轻量级锁、重量级锁之间转换对象头 Mark Word 数据转变:

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

偏向锁、轻量级锁、重量级锁之间转换

网上有一个比较全的锁升级过程:

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

锁升级过程

锁消除(Lock Elimination)

锁消除说的是虚拟机即时编译器在运行过程中,对于一些同步代码,如果检测到不可能存在共享数据竞争情况,就会删除锁。也就是说,即时编译器根据情况删除不必要的加锁操作。
锁消除的依据是逃逸分析。简单地说,逃逸分析就是分析对象的动态作用域。分三种情况:

  • 不逃逸:对象的作用域只在本线程本方法

  • 方法逃逸:对象在方法内定义后,被外部方法所引用

  • 线程逃逸:对象在方法内定义后,被外部线程所引用

即时编译器会针对对象的不同情况进行优化处理:

  • 对象栈上分配(Stack Allocations,HotSpot 不支持):直接在栈上创建对象。

  • 标量替换(Scalar Replacement):将对象拆散,直接创建被方法使用的成员变量。前提是对象不会逃逸出方法范围。

  • 同步消除(Synchronization Elimination):就是锁消除,前提是对象不会逃逸出线程。

对于锁消除来说,就是逃逸分析中,那些不会逃出线程的加锁对象,就可以直接删除同步锁。

通过代码看一个例子:

public void elimination1() {    final Object lock = new Object();    synchronized (lock) {        System.out.println("lock 对象没有只会作用域本线程,所以会锁消除。");    }}public String elimination2() {    final StringBuffer sb = new StringBuffer();    sb.append("Hello, ").append("World!");    return sb.toString();}public StringBuffer notElimination() {    final StringBuffer sb = new StringBuffer();    sb.append("Hello, ").append("World!");    return sb;}

elimination1()中的锁对象lock作用域只是方法内,没有逃逸出线程,elimination2()中的sb也就这样,所以这两个方法的同步锁都会被消除。但是notElimination()方法中的sb是方法返回值,可能会被其他方法修改或者其他线程修改,所以,单看这个方法,不会消除锁,还得看调用方法。

锁粗化(Lock Coarsening)

原则上,我们在编写代码的时候,要将同步块作用域的作用范围限制的尽量小。使得需要同步的操作数量尽量少,当存在锁竞争时,等待线程尽快获取锁。但是有时候,如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机检测到有一串零碎的操作都是对同一对象的加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
比如上面例子中的elimination2()方法中,StringBuffer的append是同步方法,频繁操作时,会进行锁粗化,最后结果会类似于(只是类似,不是真实情况):

public String elimination2() {    final StringBuilder sb = new StringBuilder();    synchronized (sb) {        sb.append("Hello, ").append("World!");        return sb.toString();    }}

或者

public synchronized String elimination3() {    final StringBuilder sb = new StringBuilder();    sb.append("Hello, ").append("World!");    return sb.toString();}

文末总结

  • 同步操作中影响性能的有两点:

    • 加锁解锁过程需要额外操作

    • 用户态与内核态之间转换代价比较大

  • synchronized 在 JDK 1.6 中有大量优化:分级锁(偏向锁、轻量级锁、重量级锁)、锁消除、锁粗化等。

  • synchronized 复用了对象头的 Mark Word 状态位,实现不同等级的锁实现。

上述就是小编为大家分享的怎么用Java实现synchronized锁同步机制了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网行业资讯频道。

免责声明:

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

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

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

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

下载Word文档

猜你喜欢

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

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

Java同步锁synchronized怎么使用

本文小编为大家详细介绍“Java同步锁synchronized怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java同步锁synchronized怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一
2023-07-05

Java中怎么利用synchronized关键字实现同步机制

Java中怎么利用synchronized关键字实现同步机制,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java的synchronized使用方法总结1. 把synchro
2023-06-18

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

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

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

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

Java中怎么利用Synchronized实现多线程同步

这期内容当中小编将会给大家带来有关Java中怎么利用Synchronized实现多线程同步,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。使用同步的原因1. 在系统中对访类要使用多线程进行访问;2. 在该类
2023-06-17

java同步机制及synchronized关键字的应用是怎样的

java同步机制及synchronized关键字的应用是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了sy
2023-06-03

Java对象级别与类级别的同步锁synchronized语法怎么用

本篇内容主要讲解“Java对象级别与类级别的同步锁synchronized语法怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java对象级别与类级别的同步锁synchronized语法怎么
2023-06-29

JAVA怎么实现乐观锁及CAS机制

本篇内容介绍了“JAVA怎么实现乐观锁及CAS机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言生活中我们看待一个事物总有不同的态度,比
2023-07-04

Java中怎么利用多线程锁实现数据同步共享

Java中怎么利用多线程锁实现数据同步共享,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。大多数应用程序要求线程互相通信来同步它们的动作。在Java程序中最简单实现同步的方法就是
2023-06-17

Android中怎么利用同步锁实现多线程

Android中怎么利用同步锁实现多线程,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、同步机制关键字synchronized 对于Java来说,最常用的同步机制就是sync
2023-05-30

go语言同步机制是什么及怎么实现

今天小编给大家分享一下go语言同步机制是什么及怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。go同步机制有:1、ch
2023-07-04

Java同步方法怎么实现

本篇内容主要讲解“Java同步方法怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java同步方法怎么实现”吧!  常用的同步方法是采用信号或加锁机制,确保资源在任意时刻至多被一个线程访问
2023-06-02

Java 中怎么利用Thread实现读写同步

Java 中怎么利用Thread实现读写同步,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1.读写者同步问题多个读者可以同时读取同一个缓冲区,但当有写者对缓冲区进行写操作时,具
2023-06-03

编程热搜

  • 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动态编译

目录