iis服务器助手广告
返回顶部
首页 > 资讯 > 精选 >怎么理解synchronized与锁的关系
  • 389
分享到

怎么理解synchronized与锁的关系

2023-06-16 13:06:46 389人浏览 薄情痞子
摘要

这篇文章主要讲解了“怎么理解synchronized与锁的关系”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解synchronized与锁的关系”吧!JVM 是如何实现 synchro

这篇文章主要讲解了“怎么理解synchronized与的关系”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解synchronized与锁的关系”吧!

JVM 是如何实现 synchronized 的?

我知道可以利用 synchronized  关键字来给程序进行加锁,但是它具体怎么实现的我不清楚呀,别急,咱们先来看个 demo :

public class demo {     public void synchronizedDemo(Object lock){   synchronized(lock){    lock.hashCode();   }  } }

上面是我写的一个 demo ,然后进入到 class 文件所在的目录下,使用 javap -v demo.class  来看一下编译的字节码(在这里我截取了一部分):

public void synchronizedDemo(java.lang.Object);   descriptor: (Ljava/lang/Object;)V   flags: ACC_PUBLIC   Code:     stack=2, locals=4, args_size=2        0: aload_1        1: dup        2: astore_2        3: monitorenter        4: aload_1        5: invokevirtual #2                  // Method java/lang/Object.hashCode:()I        8: pop        9: aload_2       10: monitorexit       11: Goto          19       14: astore_3       15: aload_2       16: monitorexit       17: aload_3       18: athrow       19: return     Exception table:        from    to  target type            4    11    14   any           14    17    14   any

应该能够看到当程序声明 synchronized 代码块时,编译成的字节码会包含 monitorenter和 monitorexit  指令,这两种指令会消耗操作数栈上的一个引用类型的元素(也就是 synchronized  关键字括号里面的引用),作为所要加锁解锁的锁对象。如果看的比较仔细的话,上面有一个 monitorenter 指令和两个 monitorexit 指令,这是  Java 虚拟机为了确保获得的锁不管是在正常执行路径,还是在异常执行路径上都能够解锁。

  • 关于 monitorenter 和 monitorexit ,可以理解为每个锁对象拥有一个锁计数器和一个指向持有该锁的线程指针:

  • 当程序执行 monitorenter 时,如果目标锁对象的计数器为 0 ,说明这个时候它没有被其他线程所占有,此时如果有线程来请求使用, Java  虚拟机就会分配给该线程,并且把计数器的值加 1

  • 目标锁对象计数器不为 0 时,如果锁对象持有的线程是当前线程, Java 虚拟机可以将其计数器加 1  ,如果不是呢?那很抱歉,就只能等待,等待持有线程释放掉

当执行 monitorexit 时, Java 虚拟机就将锁对象的计数器减 1 ,当计数器减到 0  时,说明这个锁就被释放掉了,此时如果有其他线程来请求,就可以请求成功

为什么采用这种方式呢?是为了允许同一个线程重复获取同一把锁。比如,一个 Java 类中拥有好多个 synchronized  方法,那这些方法之间的相互调用,不管是直接的还是间接的,都会涉及到对同一把锁的重复加锁操作。这样去设计的话,就可以避免这种情况。

怎么理解synchronized与锁的关系

在 Java 多线程中,所有的锁都是基于对象的。也就是说, Java 中的每一个对象都可以作为一个锁。你可能会有疑惑,不对呀,不是还有类锁嘛。但是  class 对象也是特殊的 Java 对象,所以呢,在 Java 中所有的锁都是基于对象的

在 Java6  之前,所有的锁都是"重量级"锁,重量级锁会带来一个问题,就是如果程序频繁获得锁释放锁,就会导致性能的极大消耗。为了优化这个问题,引入了"偏向锁"和"轻量级锁"的概念。所以在  Java6 及其以后的版本,一个对象有 4 种锁状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。

在 4 种锁状态中,无锁状态应该比较好理解,无锁就是没有锁,任何线程都可以尝试修改,所以这里就一笔带过了。

随着竞争情况的出现,锁的升级非常容易发生,但是如果想要让锁降级,条件非常苛刻,有种你想来可以,但是想走不行的赶脚。

阿粉在这里啰嗦一句:很多文章说,锁如果升级之后是不能降级的,其实在 HotSpot JVM 中,是支持锁降级的

锁降级发生在 Stop The World 期间,当 JVM 进入安全点的时候,会检查有没有闲置的锁,如果有就会尝试进行降级

看到 Stop The World 和 安全点 可能有人比较懵,我这里简单说一下,具体还需要读者自己去探索一番.(因为这是 JVM  的内容,这篇文章的重点不是 JVM )

在 Java 虚拟机里面,传统的垃圾回收算法采用的是一种简单粗暴的方式,就是 Stop-the-world ,而这个 Stop-the-world  就是通过安全点( safepoint )机制来实现的,安全点是什么意思呢?就是 Java 程序在执行本地代码时,如果这段代码不访问 Java 对象/调用  Java 方法/返回到原来的 Java 方法,那 Java 虚拟机的堆栈就不会发生改变,这就代表执行的这段本地代码可以作为一个安全点。当 Java 虚拟机收到  Stop-the-world 请求时,它会等所有的线程都到达安全点之后,才允许请求 Stop-the-world 的线程进行独占工作

怎么理解synchronized与锁的关系

接下来就介绍一下几种锁和锁升级

Java 对象头

在刚开始就说了, Java 的锁都是基于对象的,那是怎么告诉程序我是个锁呢?就不得不来说, Java 对象头 每个 Java  对象都有对象头,如果是非数组类型,就用 2 个字宽来存储对象头,如果是数组,就用 3 个字宽来存储对象头。在 32 位处理器中,一个字宽是 32 位;在 64  位处理器中,字宽就是 64 位咯~对象头的内容就是下面这样:

长度内容说明
32/64 bitMark Word存储对象的 hashCode 或锁信息等
32/64 bitClass Metadata Address存储到对象类型数据的指针
32/64 bitArray length数组的长度(如果是数组)
 

咱们主要来看 Mark Word 的内容:

锁状态29 bit/61 bit1 bit 是否是偏向锁2 bit 锁标志位
无锁 001
偏向锁线程 ID101
轻量级锁指向栈中锁记录的指针此时这一位不用于标识偏向锁00
重量级锁指向互斥量(重量级锁)的指针此时这一位不用于标识偏向锁10
GC 标记 此时这一位不用于标识偏向锁11
 

从上面表格中,应该能够看到,是偏向锁时, Mark Word 存储的是偏向锁的线程 ID ;是轻量级锁时, Mark Word 存储的是指向线程栈中  Lock Record 的指针;是重量级锁时, Mark Word 存储的是指向堆中的 monitor 对象的指针

偏向锁

HotSpot 的作者经过大量的研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得

基于此,就引入了偏向锁的概念

所以啥是偏向锁呢?用大白话说就是,我现在给锁设置一个变量,当一个线程请求的时候,发现这个锁是 true  ,也就是说这个时候没有所谓的资源竞争,那也不用走什么加锁/解锁的流程了,直接拿来用就行。但是如果这个锁是 false  的话,说明存在其他线程竞争资源,那咱们再走正规的流程

怎么理解synchronized与锁的关系

看一下具体的实现原理:

当一个线程第一次进入同步块时,会在对象头和栈帧中的锁记录中存储锁偏向的线程 ID 。当下次该线程进入这个同步块时,会检查锁的  Mark Word 里面存放的是不是自己的线程 ID。如果是,说明线程已经获得了锁,那么这个线程在进入和退出同步块时,都不需要花费 CAS  操作来加锁和解锁;如果不是,说明有另外一个线程来竞争这个偏向锁,这时就会尝试使用 CAS 来替换 Mark Word 里面的线程 ID 为新线程的 ID  。此时会有两种情况:

  • 替换成功,说明之前的线程不存在了,那么 Mark Word 里面的线程 ID 为新线程的 ID ,锁不会升级,此时仍然为偏向锁

  • 替换失败,说明之前的线程仍然存在,那就暂停之前的线程,设置偏向锁标识为 0 ,并设置锁标志位为 00  ,升级为轻量级锁,按照轻量级锁的方式进行竞争锁

撤销偏向锁

偏向锁使用了一种等到竞争出现时才释放锁的机制。也就说,如果没有人来和我竞争锁的时候,那么这个锁就是我独有的,当其他线程尝试和我竞争偏向锁时,我会释放这个锁

在偏向锁向轻量级锁升级时,首先会暂停拥有偏向锁的线程,重置偏向锁标识,看起来这个过程挺简单的,但是开销是很大的,因为:

  • 首先需要在一个安全点停止拥有锁的线程

  • 然后遍历线程栈,如果存在锁记录的话,就需要修复锁记录和 Mark Word ,变成无锁状态

  • 最后唤醒被停止的线程,把偏向锁升级成轻量级锁

你以为就是升级一个轻量级锁?too young too simple

偏向锁向轻量级锁升级的过程中,是非常耗费资源的,如果应用程序中所有的锁通常都处于竞争状态,偏向锁此时就是一个累赘,此时就可以通过 JVM 参数关闭偏向锁:  -XX:-UseBiasedLocking=false ,那么程序默认会进入轻量级锁状态

最后,来张图吧~

怎么理解synchronized与锁的关系

轻量级锁

如果多个线程在不同时段获取同一把锁,也就是不存在锁竞争的情况,那么 JVM 就会使用轻量级锁来避免线程的阻塞与唤醒

轻量级锁加锁

JVM 会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,称之为 Displaced Mark Word  。如果一个线程获得锁的时候发现是轻量级锁,就会将锁的 Mark Word 复制到自己的 Displaced Mark Word 中。之后线程会尝试用 CAS  将锁的 Mark Word 替换为指向锁记录的指针。

如果替换成功,当前线程获得锁,那么整个状态还是 轻量级锁 状态

如果替换失败了呢?说明 Mark Word  被替换成了其他线程的锁记录,那就尝试使用自旋来获取锁.(自旋是说,线程不断地去尝试获取锁,一般都是用循环来实现的)

自旋是耗费 CPU 的,如果一直获取不到锁,线程就会一直自旋, CPU 那么宝贵的资源就这么被白白浪费了

解决这个问题最简单的办法就是指定自旋的次数,比如如果没有替换成功,那就循环 10 次,还没有获取到,那就进入阻塞状态

但是 jdk  采用了一个更加巧妙的方法---适应性自旋。就是说,如果这次线程自旋成功了,那我下次自旋次数更多一些,因为我这次自旋成功,说明我成功的概率还是挺大的,下次自旋次数就更多一些,那么如果自旋失败了,下次我自旋次数就减少一些,就比如,已经看到了失败的前兆,那我就先溜,而不是非要“不撞南墙不回头”

自旋失败之后,线程就会阻塞,同时锁会升级成重量级锁

轻量级锁释放:

在释放锁时,当前线程会使用 CAS 操作将 Displaced Mark Word 中的内容复制到锁的 Mark Word  里面。如果没有发生竞争,这个复制的操作就会成功;如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁, CAS  操作就会失败,此时会释放锁同时唤醒被阻塞的过程

同样,来一张图吧:

怎么理解synchronized与锁的关系

重量级锁

重量级锁依赖于操作系统的互斥量( mutex  )来实现。但是操作系统中线程间状态的转换需要相对比较长的时间(因为操作系统需要从用户态切换到内核态,这个切换成本很高),所以重量级锁效率很低,但是有一点就是,被阻塞的线程是不会消耗  CPU 的

每一个对象都可以当做一个锁,那么当多个线程同时请求某个对象锁时,它会怎么处理呢?

对象锁会设置集中状态来区分请求的线程:

Contention List:所有请求锁的线程将被首先放置到该竞争队列

Entry List: Contention List 中那些有资格成为候选人的线程被移到 Entry List 中

Wait Set:调用 wait 方法被阻塞的线程会被放置到 Wait Set 中

OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为 OnDeck

Owner:获得锁的线程称为 Owner

!Owner:释放锁的线程

当一个线程尝试获得锁时,如果这个锁被占用,就会把该线程封装成一个 ObjectWaiter对象插入到 Contention List 队列的队首,然后调用  park 函数挂起当前线程

当线程释放锁时,会从 Contention List 或者 Entry List 中挑选一个线程进行唤醒

如果线程在获得锁之后,调用了 Object.wait 方法,就会将该线程放入到 WaitSet 中,当被 Object.notify 唤醒后,会将线程从  WaitSet 移动到 Contention List 或者 Entry List 中。

但是,当调用一个锁对象的 wait 或 notify 方法时,如果当前锁的状态是偏向锁或轻量级锁,则会先膨胀成重量级锁

感谢各位的阅读,以上就是“怎么理解synchronized与锁的关系”的内容了,经过本文的学习后,相信大家对怎么理解synchronized与锁的关系这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: 怎么理解synchronized与锁的关系

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

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

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

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

下载Word文档
猜你喜欢
  • 怎么理解synchronized与锁的关系
    这篇文章主要讲解了“怎么理解synchronized与锁的关系”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解synchronized与锁的关系”吧!JVM 是如何实现 synchro...
    99+
    2023-06-16
  • Java关键字synchronized原理与锁的状态详解
    目录一、Java中锁的概念二、同步关键字synchronized特性1、锁消除示例2、锁粗化示例三、synchronized关键字原理1、关于Mark Word2、锁的状态变化(1)...
    99+
    2022-11-13
    Java synchronized Java
  • JUC中的Lock锁与synchronized同步代码块问题怎么解决
    这篇文章主要介绍“JUC中的Lock锁与synchronized同步代码块问题怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“JUC中的Lock锁与synchronized同步代码块问题怎么解...
    99+
    2023-06-29
  • MYsql锁与索引的关系
    这篇文章主要讲解了“MYsql锁与索引的关系”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MYsql锁与索引的关系”吧!mysql innodb的锁是通过锁...
    99+
    2024-04-02
  • Spring事务管理下synchronized锁失效问题怎么解决
    这篇文章主要介绍“Spring事务管理下synchronized锁失效问题怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring事务管理下synchronized锁失效问题怎么解决”文章...
    99+
    2023-06-29
  • 怎样深入理解Java多线程与并发框中的synchronized 关键字
    怎样深入理解Java多线程与并发框中的synchronized 关键字,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、Class文件与对象对象头 32位JVM的对象头二、sy...
    99+
    2023-06-05
  • 怎么理解Java悲观锁与乐观锁
    本篇内容介绍了“怎么理解Java悲观锁与乐观锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1锁(Lock)在介绍悲观锁和乐观锁之前,让我们...
    99+
    2023-06-04
  • Java synchronized偏向锁的核心原理是什么
    本篇内容主要讲解“Java synchronized偏向锁的核心原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java synchronized偏向锁的核心原理是什...
    99+
    2023-06-29
  • Java中的synchronized关键字怎么用
    小编给大家分享一下Java中的synchronized关键字怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!1、synchronized锁的底层实现原理JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。...
    99+
    2023-06-25
  • Java中的synchronized锁膨胀机制怎么实现
    这篇文章主要讲解了“Java中的synchronized锁膨胀机制怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中的synchronized锁膨胀机制怎么实现”吧!synch...
    99+
    2023-06-30
  • Java对象级别与类级别的同步锁synchronized语法怎么用
    本篇内容主要讲解“Java对象级别与类级别的同步锁synchronized语法怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java对象级别与类级别的同步锁synchronized语法怎么...
    99+
    2023-06-29
  • Java中synchronized轻量级锁的核心原理是什么
    这篇文章将为大家详细讲解有关Java中synchronized轻量级锁的核心原理是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1. 轻量级锁的原理引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,...
    99+
    2023-06-29
  • Java并发系列之JUC中的Lock锁与synchronized同步代码块问题
    目录一、Lock锁二、锁的底层三、案例案例一:传统的synchronized实现案例二:Lock锁的实现四、Lock锁和synchronized的区别写在前边: 在Java服务端中,...
    99+
    2024-04-02
  • Spring事务管理下synchronized锁失效问题的解决方法
    目录一、我的思考二、图解出现的原因三、解决问题总结最近看到一个技术技术问题:synchronized锁问题? 开启10000个线程,每个线程给员工表的money字段【初始值是0】加1...
    99+
    2024-04-02
  • Objective-C中的@Synchronized关键字怎么使用
    这篇文章主要介绍“Objective-C中的@Synchronized关键字怎么使用”,在日常操作中,相信很多人在Objective-C中的@Synchronized关键字怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
    99+
    2023-07-05
  • MySQL中的锁怎么理解
    本篇内容主要讲解“MySQL中的锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“MySQL中的锁怎么理解”吧!01. 怎么认识"锁"...
    99+
    2024-04-02
  • MySQL 锁、事务隔离级别与应用的关系
    MySQL 锁的事务隔离级别与应用在数据库中,事务隔离级别是非常重要的概念,它决定了并发事务之间的隔离程度。MySQL 提供了四种事务隔离级别:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ ...
    99+
    2023-12-21
    应用 MySql锁 事务隔离级别
  • Java锁怎么理解
    本篇内容主要讲解“Java锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java锁怎么理解”吧!自旋?自旋锁如果此时拿不到锁,它不马上进入阻塞状态,而愿意等待一段时间。如果循环一定的次...
    99+
    2023-06-16
  • Java幂等性与分布式锁怎么理解
    本篇内容主要讲解“Java幂等性与分布式锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java幂等性与分布式锁怎么理解”吧!1.  什么是幂等性幂等性就是指:一个幂等操作任其...
    99+
    2023-06-05
  • 同步访问共享的可变数据synchronized与volatile关键字怎么用
    这篇文章将为大家详细讲解有关同步访问共享的可变数据synchronized与volatile关键字怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。synchronized 关键字可以保证同一时刻,只有...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作