深入理解synchronized实现原理

简介: 本文深入讲解了Java中`synchronized`关键字的实现原理。`synchronized`确保同一时刻只有一个线程能进入临界区,并保证共享变量的内存可见性。它通过monitor机制实现,作用于方法时使用ACC_SYNCHRONIZED标志,作用于代码块时使用monitorenter和monitorexit指令。每个对象都有一个与之关联的monitor,线程需获取monitor锁才能执行同步代码。Monitor内部包含_EntryList、_Owner、_WaitSet等队列,管理线程的加锁、等待和唤醒过程。

前言

java中的锁大家很快就能想到synchronized和lock,那么synchronized实现原理是怎样呢?本文将深入讲解synchronized实现原理。

简介

synchronized关键字保证方法或者代码块在运行时,同一时刻只有一个方法可以进入临界区,同时它还可以保证共享变量的内存可见性。

实现原理

synchronized 可以作用于方法和代码快,具体如下图:

作用于方法和代码块的实现原理是不同的,代码块采用的是monitorenter、monitorexit指令。而方法采用的是ACC_SYNCHRONIZED指令。

作用于代码块

csharp

代码解读

复制代码

  public synchronized void test1(){
     synchronized (this)
    {
          System.out.println("this is test method");
    }
  }

说明:从编译后的源码可以得知,代码块的同步jvm的实现采用的是关键字monitorenter、monitorexit。每个对象都与一个monitor相关联。当且仅当拥有所有者时(被拥有),monitor才会被锁定。执行到monitorenter指令的线程,会尝试去获得对应的monitor。

加锁的实现

说明:加锁采用是monitorenter指令,每个对象维护着一个记录着被锁次数的计数器, 对象未被锁定时,该计数器为0。线程进入monitor(执行monitorenter指令)时,会把计数器设置为1.当同一个线程再次获得该对象的锁的时候,计数器再次自增.当其他线程想获得该monitor的时候,就会阻塞,直到计数器为0才能成功。

释放锁的实现

说明:释放锁采用的是monitorexi指令,线程执行monitorexit指令,就会让monitor的计数器减一。如果计数器为0,表明该线程不再拥有monitor。其他线程就允许尝试去获得该monitor。

作用于方法

说明:当调用一个设置了ACC_SYNCHRONIZED标志的方法,执行线程需要先获得monitor锁,然后开始执行方法,方法执行之后再释放monitor锁,当方法不管是正常return还是抛出异常都会释放对应的monitor锁。实现的流程图如下:

小结

不管是作用于方法还是同步块,其加锁的原理都是通过monitor来实现的,那么monitor如何实现呢?

Monitor实现原理

关于Java Monitor的工作机理如图所示:

  • 获取monitor的线程,会进入_EntryList队列。
  • 当某个线程获取到对象的monitor后,进入_Owner区域,设置为当前线程,同时计数器_count加1。
  • 如果线程调用了wait()方法,则会进入_WaitSet队列。它会释放monitor锁,即将_owner赋值为null,_count自减1,进入_WaitSet队列阻塞等待。
  • 如果其他线程调用 notify() / notifyAll() ,会唤醒_WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入_Owner区域。
  • 同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。

小结

虽然monitor我们已经知道了相关的实现原理,但是monitor与对象又是如何进行关联呢?

对象与monitor关联

对象与monitor的关联其原理图如下:

对象结构

在虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对象填充。

  • 实例数据:对象真正存储的有效信息,存放类的属性数据信息,包括父类的属性信息;
  • 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
  • 对象头:Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。

对象头

而对象头又分为:Mark Word、类型指针、数组长度。

  • Mark Word:主要用于存储自身运行时数据
  • 类型指针:指向方法区中该 class的对象,JVM 通过此字段来判断当前对象是哪个类的实例
  • 数组长度:当且仅当对象是数组时才会有该字段。

Mark Word

Mark Word 用于存储对象自身的运行时数据,包含哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。

说明:当锁其膨胀成重量级锁后,其他竞争的线程进来就不会自旋了,而是直接阻塞等待,并且 Mark Word 中的内容会变成一个监视器(monitor)对象,用来统一管理排队的线程。而monitor对象与每个对象都会关联。monitor对象本质上是一个同步机制,保证了同时只有一个线程能够进入临界区,在 HotSpot的虚拟机中,是由C++类ObjectMonitor实现的。

总结

本文对于synchronized的原理进行详细的讲解,关于synchronized锁的升级将在后续的文章进行讲解。如有疑问请随时反馈。


转载来源:https://juejin.cn/post/7139424781585088549

相关文章
|
存储 搜索推荐 数据挖掘
|
存储 监控 安全
Synchronized 实现原理
Synchronized 实现原理
256 0
|
11月前
|
存储 安全 Java
synchronized 原理
`synchronized` 是 Java 中实现线程同步的关键字,通过对象头中的 Monitor 和锁机制确保同一时间只有一个线程执行同步代码。其底层依赖 Mark Word 和 Monitor 控制锁状态,支持偏向锁、轻量级锁和重量级锁的升级过程,以优化性能。同步方法和同步块在实现方式上有所不同,前者通过 `ACC_SYNCHRONIZED` 标志隐式加锁,后者通过 `monitorenter` 和 `monitorexit` 指令显式控制。此外,`synchronized` 还保证内存可见性和 Happens-Before 关系,使共享变量在多线程间正确同步。
918 0
|
人工智能 Java API
MCP客户端调用看这一篇就够了(Java版)
本文详细介绍了MCP(Model Context Protocol)客户端的开发方法,包括在没有MCP时的痛点、MCP的作用以及如何通过Spring-AI框架和原生SDK调用MCP服务。文章首先分析了MCP协议的必要性,接着分别讲解了Spring-AI框架和自研SDK的使用方式,涵盖配置LLM接口、工具注入、动态封装工具等步骤,并提供了代码示例。此外,还记录了开发过程中遇到的问题及解决办法,如版本冲突、服务连接超时等。最后,文章探讨了框架与原生SDK的选择,认为框架适合快速构建应用,而原生SDK更适合平台级开发,强调了两者结合使用的价值。
14396 33
MCP客户端调用看这一篇就够了(Java版)
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
开发工具 git
成功解决 IDEA 2020 版本 代码报错不提示的几种方案
这篇文章提供了几种解决IntelliJ IDEA 2020版本中代码报错不提示问题的方案,包括通过修改文件夹权限、暂存本地更改后进行git pull,以及在git pull后应用暂存的更改并提交代码到远程仓库的方法。
|
缓存 监控 负载均衡
将近2万字的Dubbo原理解析,彻底搞懂dubbo
市面上有很多基于RPC思想实现的框架,比如有Dubbo。今天就从Dubbo的SPI机制、服务注册与发现源码及网络通信过程去深入剖析下Dubbo。
30824 9

热门文章

最新文章