广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java实现synchronized锁同步机制
  • 880
分享到

Java实现synchronized锁同步机制

2024-04-02 19:04:59 880人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录synchronized实现原理适应性自旋(AdaptiveSpinning)锁升级Java对象头偏向锁(BiasedLocking)偏向锁获取偏向锁释放关闭偏向锁轻量级锁(Li

synchronized 是 java 内置的同步锁实现,一个关键字实现对共享资源的锁定。synchronized 有 3 种使用场景,场景不同,加锁对象也不同:

  • 普通方法:锁对象是当前实例对象
  • 静态方法:锁对象是类的 Class 对象
  • 方法块:锁对象是 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 对象头 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。这时候线程堆栈与对象头的状态如下图所示:

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

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

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

如果更新失败,检查对象头的 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 数据转变:

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

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

锁升级过程

锁消除(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锁同步机制的文章就介绍到这了,更多相关Java synchronized锁同步 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java实现synchronized锁同步机制

本文链接: https://www.lsjlt.com/news/156412.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • Java实现synchronized锁同步机制
    目录synchronized实现原理适应性自旋(AdaptiveSpinning)锁升级Java对象头偏向锁(BiasedLocking)偏向锁获取偏向锁释放关闭偏向锁轻量级锁(Li...
    99+
    2022-11-12
  • 怎么用Java实现synchronized锁同步机制
    这期内容当中小编将会给大家带来有关怎么用Java实现synchronized锁同步机制,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。synchronized 实现原理synchronized 是通过进入和...
    99+
    2023-06-25
  • Java 多线程同步 锁机制与synchronized深入解析
    打个比方:一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥...
    99+
    2022-11-15
    Java 多线程同步 锁机制
  • Java同步锁synchronized怎么使用
    本文小编为大家详细介绍“Java同步锁synchronized怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java同步锁synchronized怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一...
    99+
    2023-07-05
  • Java中怎么利用synchronized关键字实现同步机制
    Java中怎么利用synchronized关键字实现同步机制,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java的synchronized使用方法总结1.  把sy...
    99+
    2023-06-18
  • Java中的synchronized锁膨胀机制怎么实现
    这篇文章主要讲解了“Java中的synchronized锁膨胀机制怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中的synchronized锁膨胀机制怎么实现”吧!synch...
    99+
    2023-06-30
  • java synchronized 锁机制原理详解
    目录前言: 1、synchronized 的作用:2、synchronized 底层语义原理:3、 synchronized 的显式同步与隐式同步:3.1、syn...
    99+
    2022-11-12
  • Java同步锁synchronized用法的最全总结
    目录一、并发同步问题二、锁的简介三、synchronized的三种应用方式1.修饰一个实例方法2.修饰一个静态方法3.修饰一个代码块(2)锁对象为类的Class对象四、synchro...
    99+
    2023-03-21
    Java同步锁 synchronized的总结
  • Java的锁机制:synchronized和CAS详解
    目录一为什么要用锁二synchronized怎么实现的三CAS来者何人四synchronized和CAS孰优孰劣轻量级锁重量级锁总结提到Java的知识点一定会有多线程,JDK版本不断...
    99+
    2022-11-12
  • java中synchronized Lock(本地同步)锁的8种情况
    目录lock1lock2lock3lock4lock5lock6lock7lock8Lock(本地同步)锁的8种情况总结与说明: * 题目: * 1.标准访问,请问是先打印邮件还是...
    99+
    2022-11-12
  • java同步机制及synchronized关键字的应用是怎样的
    java同步机制及synchronized关键字的应用是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了sy...
    99+
    2023-06-03
  • Java中怎么利用Synchronized实现多线程同步
    这期内容当中小编将会给大家带来有关Java中怎么利用Synchronized实现多线程同步,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。使用同步的原因 在系统中对访类要使用多线程进行访问; 在该类中有 类...
    99+
    2023-06-17
  • Java同步锁Synchronized底层源码和原理剖析(推荐)
    目录1 synchronized场景回顾2 反汇编寻找锁实现原理3 synchronized虚拟机源码3.1 HotSpot源码Monitor生成3.2 HotSpot源码之Moni...
    99+
    2022-11-13
  • Java synchronized轻量级锁如何实现
    这篇“Java synchronized轻量级锁如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java&n...
    99+
    2023-07-05
  • Java synchronized重量级锁如何实现
    今天小编给大家分享一下Java synchronized重量级锁如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2023-07-05
  • Java中的synchronized 优化方法之锁膨胀机制
    目录synchronized什么是用户态和内核态?为什么分内核态和用户态?锁膨胀偏向锁偏向锁执行流程偏向锁的优点Mark Word 扩展知识:内存布局轻量级锁注意事项重量级锁总结前言...
    99+
    2022-11-13
  • Java对象级别与类级别的同步锁synchronized语法示例
    目录1. 对象级别的同步锁2. 类级别的同步锁3. 总结 Java synchronized 关键字 可以将一个代码块或一个方法标记为同步代码块。同步代码块是指同一时间只能有一个线程...
    99+
    2022-11-13
  • Java synchronized轻量级锁实现过程浅析
    目录一、什么是轻量级锁二、为什么引入轻量级锁三、轻量级锁的升级时机四、轻量级锁的演示五、轻量级锁的原理六、轻量级锁升级为重量级锁的流程七、轻量级锁的优缺点一、什么是轻量级锁 轻量级锁...
    99+
    2023-02-11
    Java synchronized轻量级锁 Java synchronized Java轻量级锁
  • Java对象级别与类级别的同步锁synchronized语法怎么用
    本篇内容主要讲解“Java对象级别与类级别的同步锁synchronized语法怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java对象级别与类级别的同步锁synchronized语法怎么...
    99+
    2023-06-29
  • Java并发系列之JUC中的Lock锁与synchronized同步代码块问题
    目录一、Lock锁二、锁的底层三、案例案例一:传统的synchronized实现案例二:Lock锁的实现四、Lock锁和synchronized的区别写在前边: 在Java服务端中,...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作