当前位置: 首页 > news >正文

Synchronized底层核心原理

前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章是关于并发编程中Synchronized锁的底层核心原理知识记录,由于篇幅原因,下篇文章将介绍各种锁的优化原理。
本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
小威在此先感谢各位大佬啦~~🤞🤞
在这里插入图片描述

🏠个人主页:小威要向诸佬学习呀
🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
目前状况🎉:24届毕业生,在一家满意的公司实习👏👏

🎁如果大佬在准备面试,可以使用我找实习前用的刷题神器哦刷题神器点这里哟
💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

以下正文开始

在这里插入图片描述

文章目录

  • synchronized锁简述
  • monitor锁的原理
  • synchronized修饰方法
  • synchronized修饰代码块
  • synchronized锁总结

synchronized锁简述

前面说到,synchronized锁用于同步实例方法,同步静态方法和同步代码块。自从Java1.6开始,就对synchronized锁进行了很多方面的优化。对其引入了偏向锁,轻量级锁,适应性自旋锁,锁粗化,锁消除等各种技术方面的优化。

synchronized锁是基于monitor锁实现的,因此在讲解synchronized锁之前,有必要了解一下monitor锁。
在这里插入图片描述

monitor锁的原理

monitor,在中文中有监视器的意思,当创建对象时,每一个创建出来的对象都会关联一个monitor对象,对于一个java对象,当拿到这个monitor对象时,这个monitor对象就会处于锁定的状态,其他对象不会再获取,synchronized锁的本质就是基于进入和退出monitor对象实现的同步方法和同步代码块。

这里首先解释一下wait,notify,notifyAll等方法的各个作用:

wait方法会让进入object监视器的线程进入到WaitSet集合中等待;
notify方法会使在object上正在WaitSet集合上等待的线程中挑一个唤醒线程
notifyAll方法会让正在WaitSet集合中等待的线程全部唤醒

而对于monitor,它是基于ObjectMonitor实现的,ObjectMonitor的主要数据结构包括:

owner:owner原本的值为null,它用来指向获取到ObjectMonitor对象的线程。当一个线程获取到ObjectMonitor对象时,这个ObjectMonitor对象就会存储在当前对象的对象头中的Mark
Word中。

WaitSet,这个是ObjectMonitor中的一个集合,同时WaitSet与wait()方法有关。当Owner线程发现条件不满足时,会调用wait方法,使线程进入WaitSet集合中变为WAITING状态。

EntryList,也是ObjectMonitor中的一个集合,同时EntryList与notify(),notifyAll()方法有关。WAITING状态下的线程会在Owner线程调用notify()或notifyAll()等方法时唤醒,但是唤醒之后并不代表着线程会立即拿到锁资源,而是需要进入EntryList集合中进行竞争。

在这里插入图片描述

模拟多线程情况下,同时访问一个被synchronized锁修饰方法时,在JVM底层中的流程如下·:

  1. 线程进入EntryList集合时,如果某个线程获取到monitor对象时,这个线程会进入owner中,同时会把monitor对象中的owner变量复制为当前的线程(拿到monitor对象的这个),并且会把monitor对象中的count变量值+1。
  2. 如果线程调用wait方法,当前的线程就会释放拿到的monitor对象,并且会把monitor对象中的owner变量值设为null,并且count的值-1。最后,当前线程会进入到WaitSet集合中等待,等候再次被唤醒。
  3. 如果是获得monitor对象的线程执行任务完成后,也会进行上面的一系列操作,但不会到WaitSet集合中等待了,因为任务已经执行完了。
    在这里插入图片描述

synchronized修饰方法

前面说到synchronized锁是基于monitor锁实现的。当synchronized锁修饰方法时,被此锁修饰的方法会比普通方法的常量池中多一个ACC_SYNCHRONIZED标识符。当线程调用了被synchronized锁修饰的方法时,会检查方法中是否设置了此标识符。

如果设置了ACC_SYNCHRONIZED标识符,那么当前的线程会首先获取monitor锁对象,然后执行同步代码中的方法,完成后会释放monitor对象。当然,在多线程情况下,只有一个线程能够获取此monitor对象,并且在该线程释放monitor对象之前,其他线程无法获取此monitor对象。因此在同一时刻,只能有一个线程拿到相同对象的synchronized锁资源。

而当synchronized锁修饰代码块时,与synchronized修饰方法略有不同,接下来详细讲解synchronized修饰代码块的情况。
在这里插入图片描述

synchronized修饰代码块

当synchronized锁修饰代码块时,synchronized关键字会被编译成monitorenter和monitorexit两条指令,其中,monitorenter会放在代码块的前面,而monitorexit会放在代码块的后面。

对于monitorenter指令:

每个对象都拥有一个monitor,当monitor被占用时,就会处于锁定状态,线程执行monitorenter指令时会获取monitor的所有权。

当monitor计数为0时,说明该monitor还未被锁定,此时线程会进入monitor并将monitor的计数器设为1,并且该线程就是monitor的所有者。

如果此线程已经获取到了monitor锁,再重新进入monitor锁的话,那么会将计时器count的值加1。

如果有线程已经占用了monitor锁,此时有其他的线程来获取锁,那么此线程将进入阻塞状态,待monitor的计时器count变为0,这个线程才会获取到monitor锁。

在这里插入图片描述

对于monitorexit指令:

首先,只有拿到了monitor锁对象的线程才会执行monitorexit指令。

其次就是,在执行monitorexit指令时,计时器count的值会减1,当count的值减到0时,当前的线程才会退出monitor,此时的线程不再是monitor的所有者,当然执行后,其他线程可以获取当前monitor锁的所有权。

通过对简单代码进行反编译来举例:

public class SynchronizedTest {
    public void synchronize(){
        synchronized (this){
            System.out.println("hello world");
        }
    }
}

执行 javap -c SynchronizedTest.class指令得到以下字节码:

public class Synchronized.SynchronizedTest {
  public Synchronized.SynchronizedTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public void synchronize();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3                  // String hello world
       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_1
      13: monitorexit
      14: goto          22
      17: astore_2
      18: aload_1
      19: monitorexit
      20: aload_2
      21: athrow
      22: return
    Exception table:
       from    to  target type
           4    14    17   any
          17    20    17   any
}

由上述编码可以看出,在synchronized修饰的代码块中,存在有monitorenter指令和monitorexit指令。
在这里插入图片描述

synchronized锁总结

因此,由以上可以得出,synchronized锁修饰方法和代码块时底层实现上是一样的,但是在修饰方法时,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的。

本篇文章就分享到这里了,后续将会分享各种锁的优化以及原理,感谢大佬认真读完支持咯 ~
在这里插入图片描述

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
希望能和诸佬们一起努力,今后进入到心仪的公司
再次感谢各位小伙伴儿们的支持🤞

在这里插入图片描述

相关文章:

  • 基于JSP的房屋销售系统设计与实现
  • Arduino UNO 可视化GT-24工业级无线透传
  • 【QT 自研上位机 与 STM32F103下位机联调>>>通信测试-基础样例-联合文章】
  • c语言的三种基本结构——初学者一定要了解哦
  • 无人驾驶:高精地图与定位
  • 【论文笔记】提高超高分辨率图像的语义分割准确性的两种方法:MagNet(CVPR2021)与FCtL(ICCV2021)
  • OGG21C微服务的安装和配置
  • Matlab之多平台雷达检测融合仿真(附源码)
  • 2022年计网《宿舍网有线无线一体化项目》总结
  • 渗透测试基础- - -windows网络安全常用dos命令
  • 【Python】近似熵,样本熵,模糊熵计算高效版
  • Java获取自增主键ID值
  • 【算法入门图论】【模板】拓扑排序|【模板】单源最短路2 |最小生成树
  • 第31讲:MySQL事务的并发问题以及事务的隔离级别
  • JavaScript 2 JavaScript 简介
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【译】理解JavaScript:new 关键字
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Android系统模拟器绘制实现概述
  • angular2 简述
  • Fabric架构演变之路
  • Java 最常见的 200+ 面试题:面试必备
  • Java超时控制的实现
  • java第三方包学习之lombok
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Python爬虫--- 1.3 BS4库的解析器
  • python学习笔记-类对象的信息
  • Redis在Web项目中的应用与实践
  • SpriteKit 技巧之添加背景图片
  • SQLServer插入数据
  • 分布式任务队列Celery
  • 官方解决所有 npm 全局安装权限问题
  • 将回调地狱按在地上摩擦的Promise
  • 浏览器缓存机制分析
  • 如何胜任知名企业的商业数据分析师?
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 原生js练习题---第五课
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ​VRRP 虚拟路由冗余协议(华为)
  • (+4)2.2UML建模图
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (C#)一个最简单的链表类
  • (python)数据结构---字典
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (算法二)滑动窗口
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...