禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 浅谈池化技术的优雅关闭
    • 浅谈守护线程与进程优雅关闭
    • 浅谈并发编程等待通知模型
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 深入理解synchronized同步机制
      • 写在文章开头
      • synchronized是什么?有什么用?
      • synchronized基础使用示例
        • 作用于静态方法
        • 作用于对象方法
        • 作用于代码块
      • 详解synchronized关键字
        • synchronized是如何工作的
        • synchronized如何保证可见性、有序性、可重入性
      • 详解synchronized锁粗化和锁消除
        • JIT阶段下的锁粗化
        • 逃逸分析下的锁消除
      • 详解synchronized中的锁升级
        • 详解锁升级过程
        • 基于jol-core代码印证
        • 更多关于jol-core
      • synchronized常见面试题
        • synchronized和ReentrantLock的区别
        • Spring上的一个单例对象,两个方法上用synchronized锁,两个线程调用,会发生阻塞吗?
      • 小结
      • 参考文献
    • 详解JUC包下的锁
    • 详解JUC包下各种锁的使用
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • 详解Java并发流程控制工具
    • Java并发容器总结
    • 深入解析CopyOnWriteArrayList
    • 详解Java并发编程volatile关键字
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • JS设计模式总结笔记
    • 小程序笔记
    • 《JavaScript教程》笔记
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2026-03-25
目录

深入理解synchronized同步机制

[toc]

# 写在文章开头

synchronized关键字算是日常编程开发中较常用的线程安全关键字,本文将基于底层工作原理和日常运用的角度深入分析该关键字,希望对你有所帮助。

我是 SharkChili,Java 开发者,Java Guide开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# synchronized是什么?有什么用?

synchronized是在多线程场景经常用到的关键字,通过synchronized将共享资源设置为临界资源,确保并发场景下共享资源操作的正确性:

# synchronized基础使用示例

# 作用于静态方法

synchronized作用于静态方法上,锁的对象为Class,这就意味着方法的调用者无论是Class还是实例对象都可以保持互斥,所以下面这段代码的结果为200:


public class SynchronizedDemo {
private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域静态类上
     */
    public synchronized static void method() {
        count++;
    }

   @Test
    public  void test() {
        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->SynchronizedDemo.method());

        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->new SynchronizedDemo().method());


        logger.info("count:{}",count);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

输出结果

22:59:44.647 [main] INFO com.sharkChili.webTemplate.SynchronizedDemo - count:20000

1
2

# 作用于对象方法

作用于方法上,则锁住的对象是调用的示例对象,如果我们使用下面这段写法,最终的结果却不是10000。



    private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域实例方法上
     */
    public synchronized  void method() {
        count++;
    }

    @Test
    public  void test() {

        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->new SynchronizedDemo().method());


        logger.info("count:{}",count);

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

输出结果

2023-03-16 21:03:44,300 INFO  SynchronizedDemo:30 - count:8786

1
2

因为synchronized 作用于实例方法,会导致每个线程获得的锁都是各自使用的实例对象,而++操作又非原子操作,导致互斥失败进而导致数据错误。 什么是原子操作呢?通俗的来说就是一件事情只要一条指令就能完成,而count++在底层汇编指令如下所示,可以看到++操作实际上是需要3个步骤完成的:

  1. 从内存将count读取到寄存器
  2. count自增
  3. 写回内存
__asm
{
        moveax,  dword ptr[i]
        inc eax
        mov dwordptr[i], eax
}
1
2
3
4
5
6

正是由于锁互斥的失败,导致两个线程同时到临界区域加载资源,获得的count都是0,经过自增后都是1,导致数据少了1。

所以正确的使用方式是多个线程使用同一个对象调用该方法

SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->demo.method());


        logger.info("count:{}",count);
1
2
3
4
5
6
7

这样一来输出的结果就正常了。

2023-03-16 23:08:23,656 INFO  SynchronizedDemo:31 - count:10000

1
2

# 作用于代码块

作用于代码块上的synchronized锁住的就是括号内的对象实例,以下面这段代码为例,锁的就是当前调用者:

//锁住当前调用实例
public void method() {
        synchronized (this) {
            count++;
        }
    }
1
2
3
4
5
6

所以我们的使用的方式还是和作用与实例方法上一样:



        SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1, 1_0000)
                .parallel()
                .forEach(i -> demo.method());


        logger.info("count:{}", count);

1
2
3
4
5
6
7
8
9
10

输出结果也是10000

2023-03-16 23:11:08,496 INFO  SynchronizedDemo:33 - count:10000

1
2

# 详解synchronized关键字

# synchronized是如何工作的

为了更好的讲解synchronized关键字的底层原理,我们给出一段synchronized作用于代码块上的方法:

public class SynchronizedDemo {


    private static int count = 0;

    /**
     * synchronized作用域实例方法内
     */
    public void method() {
        synchronized (this) {
            count++;
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1, 1_0000)
                .parallel()
                .forEach(i -> demo.method());


        System.out.println("count:" + count);
    }


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

先使用javac指令生成class文件:

javac SynchronizedDemo.java
1

然后再使用反编译指令javap获取反编译后的代码信息:

javap -c -s -v  SynchronizedDemo.class
1

最终我们可以看到method方法的字节码指令,可以看到关键字synchronized 的锁是通过monitorenter和monitorexit指令来确保线程间的同步。

 public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field count:I
         7: iconst_1
         8: iadd
         9: putstatic     #2                  // Field count:I
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

我们再将synchronized 关键字改到方法上再次进行编译和反编译:

public synchronized void method() {
            count++;        
    }
1
2
3

可以看到synchronized 实现锁的方式编程了通过ACC_SYNCHRONIZED关键字来标明该方法是一个同步方法:

 public synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field count:I
         3: iconst_1
         4: iadd
         5: putstatic     #2                  // Field count:I
         8: return
      LineNumberTable:
        line 17: 0
        line 19: 8
1
2
3
4
5
6
7
8
9
10
11
12
13

了解了不同synchronized在不同位置使用的指令之后,我们再来聊聊这些指令如何实现"锁"的。

因为JDK1.6之后提出锁升级的机制,涉及不同层面的锁的过程,这里我们直接以默认情况下最高级别的重量级锁为例展开探究。

每个线程使用的实例对象都有一个对象头,每个对象头中都有一个Mark Word,当我们使用synchronized 关键字时,这个Mark Word就会指向一个monitor。 这个monitor锁就是一种同步工具,是实现线程操作临界资源互斥的关键所在,在Java HotSpot虚拟机中,monitor就是通过ObjectMonitor实现的。

其代码如下,我们可以看到_EntryList、_WaitSet 、_owner三个关键属性:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 记录线程获取锁的次数
    _waiters      = 0,
    _recursions   = 0;  //锁的重入次数
    _object       = NULL;
    _owner        = NULL;  // 指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL;  // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;//竞争队列,和_EntryList作用类似
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

我们假设自己现在就是一个需要获取锁的线程-24,要获取ObjectMonitor锁,所以我们经过了下面几个步骤:

  1. 线程24发现_owner为空,尝试cas获取锁失败,进入步骤2。
  2. 再次自旋尝试取锁,发现_owner还是被其他线程持有,无法获取,以头插法的方式进入 _cxq 队列(这里之所以采用头插法是因为新来的线程数据可能被cpu缓存,优先被唤醒处理效率高,可能造成老线程饥饿,所以不同jvm版本针对_cxq和__EntryList队列的调度策略都有所不同)。
  3. 线程1用完锁,将count--变为0,释放锁,_owner被清空,jvm唤醒__EntryList中的队列(期间_cxq队列的数据可能会被合并到_EntryList),所有线程执行步骤4。
  4. 线程-24有机会获取_owner,尝试cas争抢并成功获取锁,_owner指向当前线程0,将count++。
  5. 线程-24执行某些方法再次尝试针对当前线程上锁,执行锁重入,累加_recursions
  6. 因为等待某些条件,线程-24主动调用wait方法释放锁,线程进入_WaitSet ,count--变为0,_owner被清空,其他线程尝试争抢锁。
  7. 因为条件为真线程24被notify()唤醒,再次进入_EntryList等待时机获取锁,成功后_owner指针再次指向线程0
  8. 这一次,我们用完临界资源,准备释放锁,count--变为0,_owner清空,其他线程继续进行monitor争抢。

thread-2.drawio

# synchronized如何保证可见性、有序性、可重入性

我们先来说说可见性,每个线程使用synchronized获得锁操作临界资源时,首先需要获取临界资源的值,为了保证临界资源的值是最新的,JMM模型规定线程必须将本地工作内存清空,到共享内存中加载最新的进行操作。

当前线程上锁后,其他线程是无法操作这个临界资源的。当前线程操作完临界资源之后,会立刻将值写回主存中,正是由于每个线程操作期间其他线程无法干扰,且临界资源数据实时同步,所以synchronized关键字保证了临界资源数据的可见性。

再来说说有序性,synchronized同步的代码块具备排他性,这就意味着同一个时刻只有一个线程可以获得锁,synchronized代码块的内部资源是单线程执行的。同时synchronized也遵守as-if-serial原则,可以当线程线程修改最终结果是可以保证最终有序性,注意这里笔者说的保证最终结果的有序性。

具体例子,某段线程得到锁Test.class之后,执行临界代码逻辑,可能会先执行变量b初始化的逻辑,在执行a变量初始化的逻辑,但是最终结果都会执行a+b的逻辑。这也就我们的说的保证最终结果的有序,而不保证执行过程中的指令有序。

 synchronized (Test.class) {
            int a=1;
            int b=2;
            int c=a+b;
        }
1
2
3
4
5

这一点,我们在上文中已经说明了,synchronized底层的维护着一个monitor对象会通过_count维护当前线程重入次数,即同一个线程连续获取锁之后,这个_count都会累加,当_count变为0时,其他线程才能争抢这把锁。

# 详解synchronized锁粗化和锁消除

# JIT阶段下的锁粗化

当jvm发现操作的方法连续对同一把锁进行加锁、解锁操作,就会对锁进行粗化,所有操作都在同一把锁中完成:

如下代码所示,该方法内部连续3次上同一把锁,存在频繁上锁执行monitorenter和monitorexit的开销:

 private static void func1() {
        synchronized (lock) {
            System.out.println("lock first");
        }

        synchronized (lock) {
            System.out.println("lock second");
        }

        synchronized (lock) {
            System.out.println("lock third");
        }
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14

这一点我们通过jclasslib查看字节码即可知晓这一点:

对此JIT编译器一旦感知到这种一个操作频繁加解同一把锁的情况,便会将锁进行粗化,最终的代码效果大概是这样:


 private static void func1() {
        synchronized (lock) {
            System.out.println("lock first");
            System.out.println("lock second");
            System.out.println("lock third");
        }
        
    }
1
2
3
4
5
6
7
8
9

# 逃逸分析下的锁消除

虚拟机在JIT即时编译运行时,对一些代码上要求同步,但是检测到不存在共享数据的锁的进行消除。

下面这段代码涉及字符串拼接操作,所以jvm会将其优化为StringBuffer或者StringBuilder,至于选哪个,这就需要进行逃逸分析了。逃逸分析通俗来说就是判断当前操作的对象是否会逃逸出去被其他线程访问到。

public String appendStr(String str1, String str2, String str3) {
        String result = str1 + str2 + str3;
        return result;
    }
1
2
3
4

例如我们上面的result ,是局部变量,没有发生逃逸,所以完全可以当作栈上数据来对待,是线程安全的,所以jvm进行锁消除,使用StringBuilder而不是Stringbuffer完成字符串拼接:

这一点我们可以在字节码文件中得到印证

关于逃逸分析可以可以参考笔者的这篇文章: 聊点硬核的逃逸分析技术:https://mp.weixin.qq.com/s?__biz=MzkwODYyNTM2MQ==&mid=2247483845&idx=1&sn=8b199b7a3456c3c3c3cc75054d5d976b&chksm=c0c6557bf7b1dc6d54bc2f09c99892eadc8f19c558de1a3c83ddb05044a4731322c25097b80d#rd (opens new window)

# 详解synchronized中的锁升级

# 详解锁升级过程

synchronized关键字在JDK1.6之前底层都是直接调用ObjectMonitor的enter和exit完成对操作系统级别的重量级锁mutex的使用,这使得每次上锁都需要从用户态转内核态尝试获取重量级锁的过程。

这种方式也不是不妥当,在并发度较高的场景下,取不到mutex的线程会因此直接阻塞,到等待队列_WaitSet 中等待唤醒,而不是原地自选等待其他线程释放锁而立刻去争抢,从而避免没必要的线程原地自选等待导致的CPU开销,这也就是我们上文中讲到的synchronized工作原理的过程。

但是在并发度较低的场景下,可能就10个线程,竞争并不激烈可能线程等那么几毫秒就可以拿到锁了,而我们每个线程却还是需要不断从用户态到内核态获取重量级锁、到_WaitSet 中等待机会的过程,这种情况下,可能功能的开销还不如所竞争的开销来得激烈。

所以JDK1.6之后,HotSpot虚拟机就对synchronized底层做了一定的优化,通俗来说根据线程竞争的激烈程度的不断增加逐步进行锁升级的策略。对应的我们先给出32位虚拟机中不同级别的锁在对象头mark word中的标识变化:

我们假设有这样一个场景,我们有一个锁对象LockObj,我们希望用它作为锁,使用代码逻辑如下所示:


synchronized(LockObj){

//dosomething
}

1
2
3
4
5
6

我们把自己当作一个线程,一开始没有线程竞争时,synchronized锁就是无锁状态,无需进行任何锁争抢的逻辑。此时锁对象LockObj的偏向锁标志位为0,锁标记为01。

后续线程1需要尝试执行该语句块,首先通过CAS修改mark word中的信息,即锁的对象LockObj的对象头偏向锁标记为1,锁标记为01,我们的线程开始尝试获取这把锁,并将线程id就当前线程号即可。

后续线程1操作锁时,只需比较一下mark word中的锁是否是偏向锁且线程id是否是线程1即可:

当我们发现偏向锁中指向的线程id不是我们时,就执行下面的逻辑:

  1. 我们尝试CAS竞争这把锁,如果成功则将锁对象的markdown中的线程id设置为我们的线程id,然后执行代码逻辑。
  2. 我们尝试CAS竞争这把锁失败,则当持有锁的线程到达安全点的时候,直接将这个线程挂起并执行锁撤销,将偏向锁升级为轻量级锁,然后持有锁的线程继续自己的逻辑,我们的线程继续等待机会。

可能很多读者对于安全点的概念不是很了解,感兴趣的读者可以移步阅读一下笔者的这篇文章:

来聊聊JVM中安全点的概念 (opens new window)

升级为轻量级锁时,偏向锁标记为0,锁标记变为是00。此时,如果我们的线程需要获取这个轻量级锁时的过程如下:

  1. 判断当前这把锁是否为轻量级锁,如果是则在线程栈帧中划出一块空间,存放这把锁的信息,我们这里就把它称为"锁记录",并将锁对象的markword复制到锁记录中。

  1. 复制成功之后,通过CAS的方式尝试将锁对象头中markword更新为锁记录的地址,并将owner指向锁对象头的markword。如果这几个步骤操作成功,则说明获取轻量级锁成功了。

  1. 如果线程CAS操作失败,则进行自旋获取锁,如果自旋超过10次(默认设置为10次)还没有得到锁则将锁升级为重量级锁,升级为重量级锁时,锁标记为0,锁状态为10。由此导致持有锁的线程进行释放时需要CAS修改mark word信息失败,发现锁已经被其他线程膨胀为重量级锁,对应释放操作改为将指针地址置空,然后唤醒其他等待的线程尝试获取锁。

经过上述的讲解我们对锁升级有了一个全流程的认识,在这里做个阶段小结:

  1. 无线程竞争,无锁状态:偏向锁标记为0,锁标记为01。
  2. 存在一定线程竞争,大部分情况下会是同一个线程获取到,升级为偏向锁,偏向标记为1,锁标记为01。
  3. 线程CAS争抢偏向锁锁失败,锁升级为轻量级锁,偏向标记为0,锁标记为00。
  4. 线程原地自旋超过10次还未取得轻量级锁,锁升级为重量级锁,避免大量线程原地自旋造成没必要的CPU开销,偏向锁标记为0,锁标记为10。

# 基于jol-core代码印证

上文我们将自己当作一个线程了解完一次锁升级的流程,口说无凭,所以我们通过可以通过代码来印证我们的描述。

上文讲解锁升级的之后,我们一直在说对象头的概念,所以为了能够直观的看到锁对象中对象头锁标记和锁状态的变化,我们这里引入一个jol工具。

		  <!--jol内存分析工具-->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
1
2
3
4
5
6

然后我们声明一下锁对象作为实验对象。

public class Lock {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12

首先是无锁状态的代码示例,很简单,没有任何线程争抢逻辑,就通过jol工具打印锁对象信息即可。

public class Lockless {
    public static void main(String[] args) {
        Lock object=new Lock();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}

1
2
3
4
5
6
7

打印结果如下,我们只需关注第一行的object header,可以看到第一列的00000001,我们看到后3位为001,偏向锁标记为0,锁标记为01,001这就是我们说的无锁状态。

com.zsy.lock.lockUpgrade.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

接下来是偏向锁,我们还是用同样的代码即可,需要注意的是偏向锁必须在jvm启动后的一段时间才会运行,所以如果我们想打印偏向锁必须让线程休眠那么几秒,这里笔者就偷懒了一下,通过设置jvm参数-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0,通过禁止偏向锁延迟,直接打印出偏向锁信息

public class BiasLock {
    public static void main(String[] args) {
        Lock object = new Lock();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());


    }
}
1
2
3
4
5
6
7
8

输出结果如下,可以看到对象头的信息为00000101,此时锁标记为1即偏向锁标记,锁标记为01,101即偏向锁。

com.zsy.lock.lockUpgrade.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

然后的轻量级锁的印证,我们只需使用Lock对象作为锁即可。

public class LightweightLock {
    public static void main(String[] args) {
        Lock object = new Lock();
        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }
}

1
2
3
4
5
6
7
8
9

可以看到轻量级锁锁标记为0,锁标记为00,000即轻量级。

com.zsy.lock.lockUpgrade.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 f1 96 02 (11101000 11110001 10010110 00000010) (43446760)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

最后就是重量级锁了,我们只需打印出锁对象的哈希码即可将其升级为重量级锁。

public class HeavyweightLock {
    public static void main(String[] args) {
        Lock object = new Lock();
        synchronized (object) {
            System.out.println(object.hashCode());
        }

        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());

        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出结果为10001010,偏向锁标记为0,锁标记为10,010为重量级锁。

1365202186
com.zsy.lock.lockUpgrade.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           8a 15 83 17 (10001010 00010101 10000011 00010111) (394466698)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8
9

# 更多关于jol-core

jol不仅仅可以监控Java进程的锁情况,在某些场景下,我们希望通过比较对象的地址来判断当前创建的实例是否是多例,是否存在线程安全问题。此时,我们就可以VM对象的方法获取对象地址,如下所示:

 public static void main(String[] args) throws Exception {
		//打印字符串aa的地址
        System.out.println(VM.current().addressOf("aa"));


    }
1
2
3
4
5
6

# synchronized常见面试题

# synchronized和ReentrantLock的区别

我们可以从三个角度来了解两者的区别:

  1. 从实现角度:synchronized是JVM层面实现的锁,ReentrantLock是属于Java API层面实现的锁,所以用起来需要我们手动上锁lock和释放锁unlock。
  2. 从性能角度:在JDK1.6之前可能ReentrantLock性能更好,在JDK1.6之后由于JVM对synchronized增加适应性自旋锁、锁消除等策略的优化使得synchronized和ReentrantLock性能并无太大的区别。
  3. 从功能角度:ReentrantLock相比于synchronized增加了更多的高级功能,例如等待可中断、公平锁、选择性通知等功能。

# Spring上的一个单例对象,两个方法上用synchronized锁,两个线程调用,会发生阻塞吗?

答案是会的,我们不妨做个实验,首先给出一个单例bean并加上synchronized关键字:

@Service
@Slf4j
public class TestService {
    public synchronized void method1() {
        log.info("method1");
        ThreadUtil.sleep(1, TimeUnit.DAYS);
    }

    public synchronized void method2() {
        log.info("method2");
        ThreadUtil.sleep(1, TimeUnit.DAYS);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后我们给出调用单例方法的示例:

CountDownLatch latch = new CountDownLatch(2);
        //拿到单例bean
        TestService testService = SpringUtil.getBean(TestService.class);
        
        new Thread(() -> {
            //调用单例bean的方法1
            testService.method1();
            latch.countDown();
        }).start();

        new Thread(() -> {
            //调用单例bean的方法2
            testService.method2();
            latch.countDown();
        }).start();

        latch.await();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

可以看到线程1完成调用之后,线程2阻塞不输出:

2025-02-25 12:26:16.842  INFO 15248 --- [       Thread-1] com.sharkChili.TestService               : method1
1

这个问题也很好解释,方法上修饰synchronized关键字,锁的对象就是当前实例,因为我们的bean是单例的,所以两个线程需要上的锁都是这个对象的实例底层的monitor对象,这就是为什么一个线程调用方法休眠后,另一个线程阻塞了:

# 小结

我是 sharkchili ,CSDN Java 领域博客专家,mini-redis的作者,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。

同时也非常欢迎你star我的开源项目mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 参考文献

关键字: synchronized详解:https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html#synchronized原理分析 (opens new window)

面渣逆袭(Java并发编程面试题八股文)必看👍 | Java程序员进阶之路 (tobebetterjavaer.com):https://tobebetterjavaer.com/sidebar/sanfene/javathread.html#_25-synchronized的实现原理 (opens new window)

内置锁(ObjectMonitor) :https://www.cnblogs.com/hongdada/p/14513036.html (opens new window)

【原理】【Java并发】【synchronized】适合中学者体质的synchronized原理:https://developer.aliyun.com/article/1656898 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
深入源码解析synchronized关键字
详解JUC包下的锁

← 深入源码解析synchronized关键字 详解JUC包下的锁→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×