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

synchronized全解析:从锁升级到性能优化,彻底掌握Java内置锁

作为Java中最常用的同步机制,synchronized背后的实现原理和优化策略值得深入理解。本文将从底层实现到高级特性,全面解析synchronized的锁机制。

文章目录

  • 1. synchronized实现原理揭秘
    • 1.1 同步的基本概念
    • 1.2 Monitor机制核心原理
    • 1.3 字节码层面分析
  • 2. 锁的膨胀升级全过程
    • 2.1 对象头与锁状态
    • 2.2 完整的锁升级流程
  • 3. synchronized如何保证三大特性
    • 3.1 原子性(Atomicity)
    • 3.2 可见性(Visibility)
    • 3.3 有序性(Ordering)
  • 4. synchronized vs ReentrantLock深度对比
    • 4.1 特性对比表格
    • 4.2 使用场景对比
  • 5. 重量级锁的价值与代价
    • 5.1 为什么需要重量级锁?
    • 5.2 性能权衡分析
  • 6. 锁优化技术详解
    • 6.1 锁粗化(Lock Coarsening)
    • 6.2 锁消除(Lock Elimination)
    • 6.3 自适应自旋(Adaptive Spinning)
  • 7. 常见问题深度解答
    • 7.1 synchronized的锁升级过程有几次自旋?
    • 7.2 synchronized锁的是什么?
    • 7.3 synchronized的锁能降级吗?
    • 7.4 synchronized是非公平锁吗?如何体现?


1. synchronized实现原理揭秘

1.1 同步的基本概念

在多线程编程中,当多个线程同时访问共享、可变的临界资源时,需要采用同步机制来保证线程安全。synchronized作为Java内置的同步机制,通过对临界资源进行序列化访问来解决并发问题。

1.2 Monitor机制核心原理

synchronized基于JVM的Monitor(监视器锁) 实现,每个Java对象都关联一个Monitor对象:

// HotSpot虚拟机中ObjectMonitor的核心数据结构(C++实现)
ObjectMonitor() {_count        = 0;     // 记录线程进入锁的次数_owner        = NULL;  // 指向持有锁的线程_WaitSet      = NULL;  // 处于wait状态的线程队列_EntryList    = NULL;  // 处于等待锁block状态的线程队列_recursions   = 0;     // 锁的重入次数
}

Monitor工作流程:

  1. 线程进入同步代码时,尝试通过monitorenter指令获取Monitor所有权
  2. 获取成功则设置_owner为当前线程,_count计数器加1
  3. 如果获取失败,线程进入_EntryList等待
  4. 线程执行完同步代码后,通过monitorexit指令释放锁,_count减1

1.3 字节码层面分析

同步代码块被编译为字节码后,会生成对应的monitorentermonitorexit指令:

public void syncMethod() {synchronized(this) {System.out.println("同步代码块");}
}// 对应的字节码:
// monitorenter  // 进入同步块
// ... 业务逻辑代码
// monitorexit   // 正常退出
// monitorexit   // 异常退出 - 保证在异常情况下也能释放锁

而同步方法则通过方法访问标志ACC_SYNCHRONIZED来实现:

public synchronized void syncMethod() {System.out.println("同步方法");
}// 方法flags中包含ACC_SYNCHRONIZED标志

2. 锁的膨胀升级全过程

2.1 对象头与锁状态

锁状态信息存储在对象的Mark Word中,HotSpot虚拟机的对象内存布局如下:

对象内存布局
对象头 Header
实例数据 Instance Data
对齐填充 Padding
Mark Word
类型指针
数组长度
锁状态信息
哈希码
GC分代年龄

32位虚拟机的Mark Word结构:

锁状态25bit4bit1bit2bit
无锁态对象的hashCode分代年龄001
偏向锁线程ID + Epoch分代年龄101
轻量级锁指向栈中锁记录的指针00
重量级锁指向Monitor的指针10
GC标记11

2.2 完整的锁升级流程

无锁状态
偏向锁
轻量级锁
重量级锁
撤销偏向锁
自旋优化

步骤1:无锁 → 偏向锁

  • 当第一个线程访问同步块时,检查对象Mark Word中的偏向锁标识
  • 如果支持偏向锁(默认开启),CAS操作将线程ID记录到Mark Word
  • 成功则进入偏向模式,后续该线程进入同步块无需同步操作

步骤2:偏向锁 → 轻量级锁

  • 当另一个线程尝试获取锁时,检查持有偏向锁的线程是否存活
  • 如果原线程已不存活,撤销偏向锁到无锁状态,重新竞争
  • 如果原线程仍存活,检查是否还需要继续持有锁
  • 发生竞争时,偏向锁升级为轻量级锁

步骤3:轻量级锁 → 重量级锁

  • 轻量级锁通过CAS自旋尝试获取锁
  • 如果自旋超过一定次数(JDK6之前默认10次,JDK6引入自适应自旋)仍未获取到锁
  • 或者有第三个线程参与竞争,轻量级锁升级为重量级锁

3. synchronized如何保证三大特性

3.1 原子性(Atomicity)

synchronized通过Monitor的互斥特性保证原子性:

public class AtomicExample {private int count = 0;private final Object lock = new Object();public void increment() {synchronized(lock) {count++; // 这个操作在同步块内是原子的}}
}

原理分析:

  • monitorentermonitorexit指令确保同步块内的所有操作作为一个不可分割的整体执行
  • 同一时刻只有一个线程能够持有Monitor锁

3.2 可见性(Visibility)

synchronized通过内存屏障和happens-before原则保证可见性:

public class VisibilityExample {private boolean flag = false;private final Object lock = new Object();public void writer() {synchronized(lock) {flag = true; // 对后续获取同一个锁的线程可见}}public void reader() {synchronized(lock) {if(flag) {// 一定能看到writer线程的修改System.out.println("Flag is true");}}}
}

实现机制:

  1. 线程释放锁时,JMM强制将工作内存中的修改刷新到主内存
  2. 线程获取锁时,JMM使该线程的工作内存无效,从主内存重新加载变量

3.3 有序性(Ordering)

synchronized通过限制指令重排序来保证有序性:

public class OrderingExample {private int a = 0;private boolean initialized = false;private final Object lock = new Object();public void init() {synchronized(lock) {a = 1;           // 不会重排序到initialized = true之后initialized = true;}}
}

有序性保证:

  • 同步块内的指令不会重排序到同步块之外
  • 不同线程按照获取锁的顺序执行同步代码,建立执行顺序的约束

4. synchronized vs ReentrantLock深度对比

4.1 特性对比表格

特性synchronizedReentrantLock
实现机制JVM内置,基于MonitorJDK实现,基于AQS
锁的获取隐式获取释放显式lock()/unlock()
可中断性不支持支持lockInterruptibly()
超时机制不支持支持tryLock(timeout)
公平性非公平锁可选择公平/非公平
条件变量一个Condition多个Condition
性能JDK6后优化,与ReentrantLock相当稳定高效
代码复杂度简单,自动释放锁复杂,需要手动释放

4.2 使用场景对比

synchronized适用场景:

// 简单的同步需求,代码简洁
public class SimpleCounter {private int count = 0;public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}

ReentrantLock适用场景:

// 复杂的同步需求,需要高级特性
public class AdvancedCounter {private int count = 0;private final ReentrantLock lock = new ReentrantLock(true); // 公平锁private final Condition notZero = lock.newCondition();public void increment() {lock.lock();try {count++;notZero.signalAll(); // 精确唤醒等待条件线程} finally {lock.unlock();}}
}

5. 重量级锁的价值与代价

5.1 为什么需要重量级锁?

尽管重量级锁性能较低,但在高竞争场景下仍然必要:

轻量级锁的局限性:

  • 自旋锁消耗CPU资源,长时间自旋得不偿失
  • 多个线程竞争时,CAS操作成功率急剧下降
  • 线程数超过CPU核心数时,自旋策略失效

重量级锁的优势:

// 高竞争场景下,重量级锁通过线程挂起避免CPU空转
public class HighContentionExample {private final Object lock = new Object();public void highContentionMethod() {synchronized(lock) {// 在100个线程竞争的场景下// 重量级锁通过线程排队,避免99个线程空转消耗CPUdoSomething();}}
}

5.2 性能权衡分析

低竞争场景性能对比:

  • 偏向锁/轻量级锁:CAS操作,用户态完成,性能极高
  • 重量级锁:系统调用,用户态/内核态切换,性能较低

高竞争场景性能对比:

  • 偏向锁/轻量级锁:大量CAS失败和自旋,CPU资源浪费
  • 重量级锁:线程挂起等待,CPU资源有效利用

6. 锁优化技术详解

6.1 锁粗化(Lock Coarsening)

// 优化前:多次锁申请释放
public void beforeOptimization() {for(int i = 0; i < 1000; i++) {synchronized(lock) {// 少量操作}}
}// 优化后:一次锁申请释放
public void afterOptimization() {synchronized(lock) {for(int i = 0; i < 1000; i++) {// 合并后的操作}}
}

6.2 锁消除(Lock Elimination)

基于逃逸分析的锁优化:

// 这个StringBuffer不会被其他线程访问,JVM会消除锁
public String concat(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1);  // append是同步方法,但锁会被消除sb.append(s2);sb.append(s3);return sb.toString();
}

开启锁消除参数:-XX:+EliminateLocks

6.3 自适应自旋(Adaptive Spinning)

JDK6引入的自适应自旋优化:

  • 根据之前自旋的成功率动态调整自旋次数
  • 如果之前自旋很少成功,则减少自旋次数
  • 如果之前自旋经常成功,则增加自旋次数

7. 常见问题深度解答

7.1 synchronized的锁升级过程有几次自旋?

在轻量级锁阶段,线程会进行自旋尝试获取锁。JDK6之前自旋次数固定(默认10次),JDK6引入自适应自旋

  • 自旋次数不再固定,由JVM根据监控数据动态决定
  • 如果之前自旋成功获取锁,则增加自旋次数
  • 如果很少自旋成功,则可能直接升级为重量级锁

7.2 synchronized锁的是什么?

synchronized锁的是对象,而不是代码或引用:

public class LockTargetExample {private final Object lock1 = new Object();private final Object lock2 = new Object();public void method1() {synchronized(lock1) {  // 锁的是lock1对象// ...}}public void method2() {synchronized(lock2) {  // 锁的是lock2对象,与method1不互斥// ...}}
}

7.3 synchronized的锁能降级吗?

不能降级。锁升级是单向过程:

  • 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 设计初衷是为了优化性能,降级带来的收益有限且实现复杂

7.4 synchronized是非公平锁吗?如何体现?

是的,synchronized是非公平锁,体现在:

public class FairnessExample {private final Object lock = new Object();public void demonstrateUnfairness() {// 线程A、B、C都在等待锁// 线程A释放锁后,新来的线程D可能比等待中的B、C先获取到锁synchronized(lock) {// 新线程可以"插队"获取锁}}
}

非公平锁的优势:

  • 减少线程切换开销,提高吞吐量
  • 避免线程唤醒的延迟
http://www.dtcms.com/a/613905.html

相关文章:

  • FPGA工程师12实战项目-基于PCIe的高速ADC采集项目
  • Linux驱动开发实战指南-中
  • 网站的开发环境是什么个人制作个网站
  • 减速机在不同工况下如何安装才更稳?
  • 哪个网站可以做线上翻译赚钱织梦系统seo网站模板
  • RocketMq面试
  • 如何在建设银行网站预约纪念币黄石有哪些做视觉网站的公司
  • 手机购物网站模板如何做好专业类网站
  • 帮企业建网站wordpress google open sans
  • AI 基础与应用开发专栏搬家说明
  • GeoSeg 框架解析
  • 预训练语言模型(Pre-trained Language Model, PLM)介绍
  • 免费域名注册网站怎么登录网站运营一个月多少钱
  • 抚顺市营商环境建设局网站公众号后台登录
  • Vue2 学习记录--路由
  • Spring Boot 热部署
  • 非易失性存储器·W25Q64使用手册解读
  • 网站统计关键词重庆市建设局网站
  • 分布式缓存技术选型指南
  • mysql主从复制配置
  • html网站模板建站手机建网站推广
  • Vue 项目实战《尚医通》,预约挂号的路由与静态搭建,笔记36
  • 网站建设 风险防控网站首页的head标签内
  • 多用户跨学科交流系统(3):评论模块与 Spring Boot 全局异常处理
  • 前后端分离部署学之思在线考试系统
  • 在树莓派4B上部署ONNX格式的YOLOv8-Pose
  • websocket和传统socket有何差别?是属于一个子集还是完全不同?
  • 双指针问题2(c++)
  • 龙岩市城乡规划建设局网站三只松鼠的网络营销方式
  • Docker容器使用手册——入门篇(上)