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

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

文章目录

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

1. synchronized实现原理揭秘

1.1 同步的基本概念

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

1.2 Monitor机制核心原理

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

java 复制代码
// 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指令:

java 复制代码
public void syncMethod() {
    synchronized(this) {
        System.out.println("同步代码块");
    }
}

// 对应的字节码:
// monitorenter  // 进入同步块
// ... 业务逻辑代码
// monitorexit   // 正常退出
// monitorexit   // 异常退出 - 保证在异常情况下也能释放锁

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

java 复制代码
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结构:

锁状态 25bit 4bit 1bit 2bit
无锁态 对象的hashCode 分代年龄 0 01
偏向锁 线程ID + Epoch 分代年龄 1 01
轻量级锁 指向栈中锁记录的指针 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的互斥特性保证原子性:

java 复制代码
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原则保证可见性:

java 复制代码
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通过限制指令重排序来保证有序性:

java 复制代码
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 特性对比表格

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

4.2 使用场景对比

synchronized适用场景:

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

ReentrantLock适用场景:

java 复制代码
// 复杂的同步需求,需要高级特性
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核心数时,自旋策略失效

重量级锁的优势:

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

5.2 性能权衡分析

低竞争场景性能对比:

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

高竞争场景性能对比:

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

6. 锁优化技术详解

6.1 锁粗化(Lock Coarsening)

java 复制代码
// 优化前:多次锁申请释放
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)

基于逃逸分析的锁优化:

java 复制代码
// 这个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锁的是对象,而不是代码或引用:

java 复制代码
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是非公平锁,体现在:

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

非公平锁的优势:

  • 减少线程切换开销,提高吞吐量
  • 避免线程唤醒的延迟
相关推荐
任子菲阳2 小时前
学Java第四十五天——斗地主小游戏创作
java·开发语言·windows
czhc11400756632 小时前
Java1112 基类 c#vscode使用 程序结构
android·java·数据库
嫂子的姐夫3 小时前
23-MD5+DES+Webpack:考试宝
java·爬虫·python·webpack·node.js·逆向
缪懿3 小时前
JavaEE:多线程基础,多线程的创建和用法
java·开发语言·学习·java-ee
Chan163 小时前
Java 集合面试核心:ArrayList/LinkedList 底层数据结构,HashMap扩容机制详解
java·数据结构·spring boot·面试·intellij-idea
Boop_wu3 小时前
[Java EE] 多线程 -- 初阶(2)
java·开发语言·jvm
q***98523 小时前
Spring Boot(快速上手)
java·spring boot·后端
凌凌03 小时前
macOS安装SDKMAN
java
百***92023 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
java·spring boot·后端