探究synchronized底层原理(基于JAVA8源码分析)

简介: JVM支持方法级和方法内部一段指令序列的同步,都用同步锁(monitor)实现synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入临界区,同时它还可以保证共享变量的内存可见性Java中每一个对象都可以作为锁,这是synchronized实现同步的基础1.

JVM支持方法级和方法内部一段指令序列的同步,都用同步锁(monitor)实现

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

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础
1. 普通同步方法,锁是当前实例对象
2. 静态同步方法,锁是当前类的class对象
3. 同步方法块,锁是括号里面的对象

当一个线程访问同步代码块

  • 首先得到锁才能执行同步代码
  • 当退出或者抛异常时必须释放锁

如何来实现这个机制呢?

实现原理

img_84780c72200180743de6beb12b2e81d9.png
看一段简单的代码

img_1eb3f46700e00fa128ebd16b573626f9.png
生成的class文件信息

JVM的同步(synchronization)是monitor的进入和退出实现的,无论是显式同步(有明确的monitorenter和monitorexit指令),还是隐式同步(无需通过字节码指令控制,依赖方法调用和返回指令实现)
Java中,同步用的最多的可能就是经synchronized修饰的同步方法
同步方法并不是用monitorenter和monitorexit实现的,而是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED标志来隐式实现的0
.monitorenter和monitorexit指令用于编译synchronized同步0.语句块

JVM从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区区分是否为同步方法
调用时,调用指令将会检查方法的ACC_SYNCHRONIZED是否设置,是则执行线程将先获得同步锁,然后执行方法,最后完成方法时释放同步锁
执行期间,其他线程都无法获得同一个锁,若运行期间抛异常并且方法内部无法处理时,所持有的锁将在抛异常到同步方法之外时自动释放

同步代码块

monitorenter插入到同步代码块的开始位置
monitorexit插入到结束位置
JVM需要保证每一个monitorenter都有一个monitorexit对应
任何对象都有一个monitor与之关联,且当一个monitor被持有之后,他将处于锁定状态
线程执行到monitorenter时,将会尝试获取对象的monitor所有权,即尝试获取对象的锁

同步方法

synchronized方法会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令
在JVM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,
而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位 置1,表该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象

深入之前我们需要了解两个重要的概念:Java对象头,Monitor。

Java对象头

synchronized用的锁存在Java对象头
对象头主要包括

  • Klass Pointer(类型指针)
    对象指向它的类元数据的指针
    JVM通过该指针确定该对象是何类的实例
  • Mark Word(标记字段)
    存储对象的运行时数据
    是实现轻量级锁和偏向锁的关键

Mark Word

存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等

Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度


img_855363c17689b54f0f393103bc70b156.jpe
Java对象头的存储结构(32位VM)

对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):


img_2846cd5489454fa450ed398cba1b511b.jpe

Monitor

可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。
和万物皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁
Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。
每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下


img_bec288913afc96f735fb909b3075e5b8.png

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL
EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程
RcThis:表示blocked或waiting在该monitor record上的所有线程的个数
Nest:用来实现重入锁的计数
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)
Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

目录
相关文章
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
537 2
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
467 0
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
469 0
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
1012 58
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
769 24
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
1167 13
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
214 1
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
11698 1
|
存储 安全 Java
深入探究Java中ThreadLocal的工作原理和用途
总结起来,ThreadLocal是Java多线程编程中一个非常有用的工具,通过为每个线程分配独立的变量副本,实现线程隔离,避免资
296 9