多线程 | synchronized的底层原理

news2024/11/15 15:37:23

目录

  • 1.它做了什么
  • 2.什么是Monitor
    • 如何减少用户态和内核态的切换
  • 3.对象头和内置锁 (ObjectMonitor)
    • 3.1对象头
    • 3.2内置锁 (ObjectMonitor)
    • 3.3wait方法底层
    • 3.4notify 方法的底层实现
  • 4.总结

1.它做了什么

使用synchronized的修饰的代码块如下,那么jvm是如何编译它的,做了什么,我们可以通过 javap -v 进行反编译下;

public class MyTest1 {
    public void method1() throws InterruptedException {
        Object object = new Object();
        synchronized (object){
            System.out.println("mingJing is the most handsome");
        }
    }
    public void method2() throws InterruptedException {
        Object object = new Object();
        synchronized (object){
            System.out.println("mingJing is the most handsome");
            throw new RuntimeException();
        }
    }
    public synchronized void method3(){
        System.out.println("mingJing is the most handsome");
    }
}

编译结果如下

/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/javap -v /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
Classfile /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
  Last modified 2021-7-2; size 1053 bytes
  MD5 checksum 984cf67a767e9074756355f0028e7b22
  Compiled from "MyTest1.java"
public class com.mingjing.concurrency.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#29         // java/lang/Object."<init>":()V
   #2 = Class              #30            // java/lang/Object
   #3 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #33            // mingJing is the most handsome
   #5 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #36            // java/lang/RuntimeException
   #7 = Methodref          #6.#29         // java/lang/RuntimeException."<init>":()V
   #8 = Class              #37            // com/mingjing/concurrency/MyTest1
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/mingjing/concurrency/MyTest1;
  #16 = Utf8               method1
  #17 = Utf8               object
  #18 = Utf8               Ljava/lang/Object;
  #19 = Utf8               StackMapTable
  #20 = Class              #37            // com/mingjing/concurrency/MyTest1
  #21 = Class              #30            // java/lang/Object
  #22 = Class              #38            // java/lang/Throwable
  #23 = Utf8               Exceptions
  #24 = Class              #39            // java/lang/InterruptedException
  #25 = Utf8               method2
  #26 = Utf8               method3
  #27 = Utf8               SourceFile
  #28 = Utf8               MyTest1.java
  #29 = NameAndType        #9:#10         // "<init>":()V
  #30 = Utf8               java/lang/Object
  #31 = Class              #40            // java/lang/System
  #32 = NameAndType        #41:#42        // out:Ljava/io/PrintStream;
  #33 = Utf8               mingJing is the most handsome
  #34 = Class              #43            // java/io/PrintStream
  #35 = NameAndType        #44:#45        // println:(Ljava/lang/String;)V
  #36 = Utf8               java/lang/RuntimeException
  #37 = Utf8               com/mingjing/concurrency/MyTest1
  #38 = Utf8               java/lang/Throwable
  #39 = Utf8               java/lang/InterruptedException
  #40 = Utf8               java/lang/System
  #41 = Utf8               out
  #42 = Utf8               Ljava/io/PrintStream;
  #43 = Utf8               java/io/PrintStream
  #44 = Utf8               println
  #45 = Utf8               (Ljava/lang/String;)V
{
  public com.mingjing.concurrency.MyTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mingjing/concurrency/MyTest1;

  public void method1() throws java.lang.InterruptedException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String mingJing is the most handsome
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
        21: monitorexit
        22: goto          30
        25: astore_3
        26: aload_2
        27: monitorexit
        28: aload_3
        29: athrow
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 12
        line 14: 20
        line 15: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  this   Lcom/mingjing/concurrency/MyTest1;
            8      23     1 object   Ljava/lang/Object;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
    Exceptions:
      throws java.lang.InterruptedException

  public void method2() throws java.lang.InterruptedException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String mingJing is the most handsome
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: new           #6                  // class java/lang/RuntimeException
        23: dup
        24: invokespecial #7                  // Method java/lang/RuntimeException."<init>":()V
        27: athrow
        28: astore_3
        29: aload_2
        30: monitorexit
        31: aload_3
        32: athrow
      Exception table:
         from    to  target type
            12    31    28   any
      LineNumberTable:
        line 18: 0
        line 19: 8
        line 20: 12
        line 21: 20
        line 22: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  this   Lcom/mingjing/concurrency/MyTest1;
            8      25     1 object   Ljava/lang/Object;
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 28
          locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
    Exceptions:
      throws java.lang.InterruptedException

  public synchronized void method3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #4                  // String mingJing is the most handsome
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 26: 0
        line 27: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/mingjing/concurrency/MyTest1;
}
SourceFile: "MyTest1.java"

Process finished with exit code 0

在每个方法中都会有个monitorenter和monitorexit配套使用,有时monitorexit可能会有多个。

2.什么是Monitor

JVM的同步,是基于进入与退出监视器对象(Monitor)来实现的,也即是管程;每个对象实例都会有个Monitor对象,这是JVM分配的。Monitor对象会和java对象一同创建并销毁。Monitor对象是由C++来实现的。

当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于堵塞状态的线程都会被放到该列表当中(wait方法可查)。接下来,当线程获取到对象的Monitor时,Monitor是依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其他线程无法再获取到该mutex。

如果线程调用了wait方法,那么该线程就会释放掉所有的mutex,并且该线程会进入到WaitSet集合中,等待下一次被其他线程调用notify或者notifyAll唤醒。如果当前线程顺利执行完毕,那么它也会释放掉所持有的mutex。

总结一下:同步锁在这种实现方式当中,因为Monitor是依赖于底层的操作系统实现,线程被阻塞后便会进入到内核调度状态,这样就存在用户态与内核态之前的切换,所以会增加性能开销。通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应于一个可称为「互斥锁」的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。

如何减少用户态和内核态的切换

自旋。其原理是:当发生对Monitor的争抢时,若Owner能够在很短的时间内释放掉锁,则那些正在争抢的线程就可以稍微等待下(空转)。 在Owner线程释放锁之后,争抢线程可能会立刻释放锁,从而避免了系统阻塞。不过,当Owner运行时间超过了临界值后,争抢线程自旋一段时间后依然无法获取到锁,这时争抢线程则会停止自旋进入到阻塞状态。

3.对象头和内置锁 (ObjectMonitor)

因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如 JDK 中的 synchronized 锁优化 和 JVM 中对象年龄升级等等。要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备。

3.1对象头

请参考这篇文章
《Java 对象的内存布局》

3.2内置锁 (ObjectMonitor)

通常所说的对象的内置锁,是对象头 Mark Word 中的重量级锁指针指向的 monitor 对象,该对象是在 HotSpot 底层 C++ 语言编写的 (openjdk 里面看),简单看一下代码:

//结构体如下
ObjectMonitor::ObjectMonitor() {  
  _header       = NULL;  
  _count       = 0;  
  _waiters      = 0,  
  _recursions   = 0;       //线程的重入次数
  _object       = NULL;  
  _owner        = NULL;    //标识拥有该monitor的线程
  _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
  _WaitSetLock  = 0 ;  
  _Responsible  = NULL ;  
  _succ         = NULL ;  
  _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
  FreeNext      = NULL ;  
  _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
  _SpinFreq     = 0 ;  
  _SpinClock    = 0 ;  
  OwnerIsThread = 0 ;  
}

ObjectMonitor 队列之间的关系转换可以用下图表示:
在这里插入图片描述
对象内置锁ObjectMonitor流程:
● 所有期待获得锁的线程,在锁已经被其它线程拥有的时候,这些期待获得锁的线程就进入了对象锁的entry set区域。
● 所有曾经获得过锁,但是由于其它必要条件不满足而需要wait的时候,线程就进入了对象锁的wait set区域 。
● 在wait set区域的线程获得Notify/notifyAll通知的时候,随机的一个Thread(Notify)或者是全部的Thread(NotifyALL)从对象锁的wait set区域进入了entry set中。
在这里插入图片描述
既然提到了waitSet 和EntryList(_cxq 队列后面会说),那就看一下底层的 wait 和 notify 方法,其实在java的wait(long time)方法中也提到了
在这里插入图片描述

3.3wait方法底层

wait 方法底层的实现过程如下,可以访问openJDK:http://hg.openjdk.java.net/jdk8u/hs-dev/hotspot/file/ae5624088d86/src/share/vm/runtime/synchronizer.cpp

//1.调用ObjectSynchronizer::wait方法
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  /*省略 */
  //2.获得Object的monitor对象(即内置锁)
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  //3.调用monitor的wait方法
  monitor->wait(millis, true, THREAD);
  /*省略*/
}
  //4.在wait方法中调用addWaiter方法
  inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  /*省略*/
  if (_WaitSet == NULL) {
    //_WaitSet为null,就初始化_waitSet
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
    //否则就尾插
    ObjectWaiter* head = _WaitSet ;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}
  //5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park  也就是wait

通过 object 获得内置锁 (objectMonitor),通过内置锁将 Thread 封装成 ojectWaiter 对象,然后 addWaiter 将它插入以_waitSet 为首结点的等待线程链表中去,最后释放锁。

3.4notify 方法的底层实现

//1.调用ObjectSynchronizer::notify方法
    void ObjectSynchronizer::notify(Handle obj, TRAPS) {
    /*省略*/
    //2.调用ObjectSynchronizer::inflate方法
    ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
    //3.通过inflate方法得到ObjectMonitor对象
    ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
    /*省略*/
     if (mark->has_monitor()) {
          ObjectMonitor * inf = mark->monitor() ;
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
          return inf 
      }
    /*省略*/ 
      }
    //4.调用ObjectMonitor的notify方法
    void ObjectMonitor::notify(TRAPS) {
    /*省略*/
    //5.调用DequeueWaiter方法移出_waiterSet第一个结点
    ObjectWaiter * iterator = DequeueWaiter() ;
    //6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
    /**省略*/
  }

通过 object 获得内置锁 (objectMonitor),调用内置锁的 notify 方法,通过waitset 结点移出等待链表中的首结点,将它置于EntrySet 中去,等待获取锁。注意:notifyAll 根据 policy 不同可能移入EntryList 或者cxq 队列中,此处不详谈。

4.总结

上面所介绍的通过 synchronzied 实现同步用到了对象的内置锁 (ObjectMonitor),而在 ObjectMonitor 的函数调用中会涉及到 Mutex lock 等特权指令,那么这个时候就存在操作系统用户态和核心态的转换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,这也是为什么早期的 synchronized 效率低的原因。在 jdk1.6 之后,从 jvm 层面做了很大的优化,下一篇我们主要介绍做了哪些优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2108935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PyQt6 应用程序】短剧原视频直接生成解说视频精简版

在当今视频内容创作日益繁荣的时代,利用自动化工具进行视频编辑和二次创作已成为提高生产效率和创作水平的重要手段。本文将介绍如何使用PyQt6创建一个应用程序,该程序能够自动提取视频中的解说和原声部分,并使用人工智能生成配套的解说视频,从而生成具有独特风格的新视频内…

Oracle OCP认证值得考吗? 需要门槛吗?

随着数据量的爆炸性增长和企业对数据依赖性的提升&#xff0c;对数据库专业人士的需求也在不断上升。OCP认证&#xff0c;作为Oracle公司提供的权威认证之一&#xff0c;长期以来被视为数据库专业人士技能和知识水平的重要标志。 但随着技术的发展和认证种类的增多&#xff0c;…

基于百度AIStudio飞桨paddleRS-develop版道路模型开发训练

基于百度AIStudio飞桨paddleRS-develop版道路模型开发训练 参考地址&#xff1a;https://aistudio.baidu.com/projectdetail/8271882 基于python35paddle120env环境 预测可视化结果&#xff1a; &#xff08;一&#xff09;安装环境&#xff1a; 先上传本地下载的源代码Pad…

数据分析:R语言计算XGBoost线性回归模型的SHAP值

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍SHAP用途计算方法:应用加载R包导入数据数据预处理函数模型介绍 SHAP(SHapley Additive exPlanations)值是一种解释机器学习模型预测的方法。它基于博弈论中的Shapley值概念,…

Linux高性能服务器编程 总结索引 | 第3章:TCP协议详解

和IP协议相比&#xff0c;TCP协议 更靠近应用层&#xff0c;因此在应用程序中具有 更强的可操作性。一些重要的socket选项都和TCP协议相关 TCP头部信息。TCP头部信息出现在 每个TCP报文段中&#xff0c;用于指定 通信的源端口号、目的端口号&#xff0c;管理TCP连接&#xff0…

使用ffmpeg在视频中绘制矩形区域

由于项目需要对视频中的人脸做定位跟踪&#xff0c; 我先使用了人脸识别算法&#xff0c;对视频中的每个帧识别人脸、通过人脸库比对&#xff0c;最终记录坐标等信息。 然后使用ffmpeg中的 drawbox 滤镜功能&#xff0c;选择性的绘制区域。从而实现人脸定位跟踪 1、drawbox …

C++项目引入开源库bit7z

摘要&#xff1a; 公司C项目需要能解压缩.tar文件&#xff0c;关键是要在Windows环境下&#xff0c;tar格式主要是Linux中用的压缩文件&#xff0c;还要考虑到用户可能没有Windows自带的tar命令&#xff0c;最终解决方案就是一步到位&#xff0c;考虑到后续的功能拓展引入第三方…

尚品汇-延迟插件实现订单超时取消(四十五)

目录&#xff1a; &#xff08;1&#xff09;延迟插件封装 &#xff08;2&#xff09;基于延迟插件测试 如何保证消息幂等性&#xff1f; &#xff08;3&#xff09;改造订单service-order模块-实现订单超时取消 &#xff08;1&#xff09;延迟插件封装 把消息带过去&#…

computed计算属性及方法对比和循环遍历统计以及watch和watchEect监听的用法

1.computed计算属性及方法对比 1.了解computed计算属性和用法 在我们的一些应用中可以看的应用会给我们提供一些计算类的功能比如取名&#xff0c;它会给你提供两个输入框&#xff0c;然后在你给这两个输入框输入值的时候会在下方生成你输入这个两个值的结合值&#xff0c;就…

Java使用类加载器解决类冲突,多版本jar共存

Java使用类加载器解决类冲突 1、案例说明2、打包新版本POI并将要调用的方法封装2.1、POM文件2.2、封装的方法 3、要使用多个POI版本的项目3.1、打包前面的项目生成一个jar包3.1、POM文件3.2、类加载器代码3.3、Jar加载工具3.4、最终调用 1、案例说明 项目中已经有了一个旧版本…

【后端开发】PHP、go语言、Java、C++、Linux开发等急招中......

本周高薪急招后端开发岗位推荐&#xff0c;PHP、go语言、Java、C、Linux开发等岗位都在热招&#xff0c;月薪最高35K&#xff0c;还不快来&#xff01;&#xff01; 抓紧投递&#xff0c;早投早入职&#xff01; &#x1f447;点击职位名称查看详情&#x1f447; PHP 薪资&…

Leetcode每日刷题之102.二叉树的层序遍历

1.题目解析 本题是关于二叉树的层序遍历&#xff0c;不过这里的难点是如何将每一层的数据存储在数组并将整体存储在一个二维数组中&#xff0c;具体的算法原理我们在下面给出 2.算法原理 关于将每层数据分别存储在不同数组中&#xff0c;我们可以定义一个levelSize变量来存储栈…

网络编程(TCP+网络模型)

【1】TCP 初版服务器 #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h…

【学习笔记】SSL/TLS如何运用加密工具

一、前文回顾&#xff1a; 1、SSL/TLS有3个目的&#xff0c;分别由不同密码学工具提供 Confidentiality&#xff08;保密性&#xff09;&#xff1a;数据只有Client和Server才能访问&#xff0c;由Encryption&#xff08;加密&#xff09;所提供Integrity&#xff08;完整性&…

【话题讨论】VS Code:倍增编程动力,实现效率飞跃

目录 引言 一、详情介绍 功能特点 使用场景 提高工作效率 二、效率对比 2.1 高度可定制性与丰富的插件生态 2.2 智能的代码补全与导航 2.3 内置的调试器与版本控制集成 2.4 轻量级与跨平台 2.5 选择合适工具的重要性 2.6 实际案例或数据展示 三、未来趋势 3.1 编…

iOS——Block与内存管理

需要内存管理的情况 1、对象类型的auto变量。 2、引用了 __block 修饰符的变量。 三种block类型 全局类型 &#xff08;NSGlobalBlock&#xff09; 如果一个block里面没有访问普通局部变量(也就是说block里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局…

FPGA开发:可编程逻辑器件概述

PLD 1、什么是PLD&#xff1f; PLD指Programmable Logic Device&#xff0c;翻译为"可编程逻辑器件"。是20世纪70年代发展起来的一种新的集成电路&#xff0c;是一种半定制的集成电路。 PLD具有逻辑功能实现灵活。集成度高、处理速度快的特点。 PLD就像是一个可定…

【Vue】pnpm创建Vue3+Vite项目

初始化项目 &#xff08;1&#xff09;cmd切换到指定工作目录&#xff0c;运行pnpm create vue命令&#xff0c;输入项目名称后按需安装组件 &#xff08;2&#xff09;使用vs code打开所创建的项目目录&#xff0c;Ctrl~快捷键打开终端&#xff0c;输入pnpm install下载项目…

IDEA运行Java程序提示“java: 警告: 源发行版 11 需要目标发行版 11”

遇到这个提示一般是在pom.xml中已经指定了构建的Java版本环境是11例如(此时添加了build插件的情况下虽然不能直接运行代码但是maven是可以正常打包构建)&#xff1a; <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><…

Vue初学-简易计算器

最近在学习Vue的指令&#xff0c;做了一个简易计算器&#xff0c;比较适合刚入门的人参考学习。用到的知识点有&#xff1a; 1.插值表达式 2.v-model&#xff0c;双向绑定、-、*、/、**等操作符 3.v-show&#xff0c;控制操作数2是否显示&#xff0c;乘方时不显示操作数2 4.met…