Java并发编程——synchronized的实现原理与应用

Java synchronized 的实现原理与应用

synchronized 是 Java 提供的一种内置关键字,用于实现线程同步,确保多线程环境下对共享资源的互斥访问。它是 JVM 层面的机制,简单易用,但底层实现较为复杂。下面我从实现原理 和应用两个方面详细解释。

1. 实现原理

synchronized 的核心是基于 Monitor(监视器) 机制实现的 Monitor 是对象头(Object Header)中的一部分,包含锁信息。JVM 通过字节码指令和锁优化来保证线程安全。

1.1 基本工作流程
  • 加锁(Lock) :当线程执行 synchronized 时,JVM 插入 monitorenter 字节码指令。线程尝试获取对象的 Monitor:
    • 如果 Monitor 空闲,线程持有它(成为 Owner),进入同步块。
    • 如果已被其他线程持有,当前线程进入阻塞队列(等待队列),直到 Owner 线程释放。
  • 解锁(Unlock) :执行到同步块末尾或异常时,插入 monitorexit 字节码,释放 Monitor,其他等待线程竞争获取。
  • 异常处理 :即使发生异常,monitorexit 也会自动执行(通过异常表机制),确保锁释放。
1.2 锁存储与优化(HotSpot JVM 实现)

synchronized 的锁不是简单的互斥锁,而是通过锁升级机制优化性能,避免不必要的开销。锁信息存储在对象头中(Mark Word,64 位中约占 64 位用于锁状态)。

锁升级过程(从低到高开销):

  1. 无锁状态:对象未被同步时。
  2. 偏向锁(Biased Locking):适用于单线程或低竞争场景。首次加锁时,Mark Word 记录线程 ID,后续同一线程无需 CAS(Compare-And-Swap)即可获取锁。竞争时升级。
  3. 轻量级锁(Lightweight Locking):中等竞争。线程在栈帧中创建锁记录(Lock Record),通过 CAS 更新 Mark Word 指针指向它。失败时自旋重试(自适应自旋,避免立即阻塞)。
  4. 重量级锁(Heavyweight Locking):高竞争。Mark Word 指向 Monitor 对象(内核态数据结构),线程阻塞/唤醒需 OS 调度(上下文切换,开销大)。
锁类型 适用场景 存储位置 优化机制 开销
偏向锁 单线程访问 Mark Word(线程 ID) 无 CAS,直接偏向 最低
轻量级锁 低竞争多线程 Mark Word + 栈锁记录 CAS + 自旋 低
重量级锁 高竞争 Mark Word + 内核 Monitor OS 阻塞/唤醒 高
  • 升级触发 :竞争加剧时自动升级(不可逆)。JVM 参数如 -XX:BiasedLockingStartupDelay=0 可控制偏向锁延迟启用。
  • 原子性与可见性 :synchronized 确保原子性 (操作不可中断)和可见性 (释放锁时刷新缓存,其他线程可见最新值)。底层依赖内存屏障(Memory Barrier)指令(如 mfence)。
1.3 底层字节码示例

编译后字节码:

复制代码
0: aload_1          // 加载 obj
1: dup
2: astore_2         // 备份 obj
3: monitorenter     // 加锁
4: aload_2          // ...
...                // 同步代码
N: aload_2
N+1: monitorexit    // 解锁
N+2: goto N+5
N+3: astore_2       // 异常时释放锁
N+4: aload_2
N+5: monitorexit
N+6: athrow         // 重新抛异常
2. 应用

synchronized 广泛用于线程安全场景,如共享变量、单例模式、线程池等。它简单,但需注意死锁风险(多锁嵌套)。优先用于简单同步;复杂场景可考虑 ReentrantLock(更灵活)。

2.1 常见应用形式
  • 同步方法 :修饰实例方法(锁 this)或静态方法(锁 Class 对象)。

    java 复制代码
    public synchronized void increment() { i++; }  // 锁 this
    public static synchronized void staticIncrement() { i++; }  // 锁 Counter.class
  • 同步代码块 :粒度更细,指定任意对象作为锁。

    java 复制代码
    synchronized (lockObj) {  // lockObj 可为任意对象
        // 临界区代码
    }
2.2 典型应用场景
场景 应用方式 说明
计数器线程安全 同步方法或代码块包裹 i++ 防止竞态条件(如前文 Counter 示例)。
单例模式 静态同步方法或双重检查锁定 确保实例唯一:synchronized (Singleton.class) { if (instance == null) instance = new ...; }。
生产者-消费者 同步块 + wait()/notify() 锁同一对象,wait() 释放锁,notify() 唤醒。
集合线程安全 同步包装:Collections.synchronizedList(list) 内部用 synchronized 包装方法。
数据库连接池 同步获取/释放连接 避免并发超限。
2.3 示例:线程安全计数器
java 复制代码
class SafeCounter {
    private int count = 0;
    private final Object lock = new Object();  // 专用锁对象

    public void increment() {
        synchronized (lock) {  // 细粒度锁
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

// 多线程测试(类似前文)
public class SyncDemo {
    public static void main(String[] args) {
        SafeCounter counter = new SafeCounter();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    counter.increment();
                }
            }));
        }
        threads.forEach(Thread::start);
        threads.forEach(t -> {
            try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); }
        });
        System.out.println(counter.getCount());  // 输出: 100000(精确)
    }
}
  • 无 synchronized:可能输出 < 100,000(丢失更新)。
  • 优势 :简单、无需显式锁管理;劣势:不可中断,性能在高竞争时较低。
3. 注意事项与最佳实践
  • 性能 :低竞争用 synchronized,高竞争考虑 ConcurrentHashMap 或 Lock。
  • 死锁避免:统一加锁顺序、超时机制。
  • Java 版本演进:JDK 6 引入锁优化,提升性能;JDK 8+ 偏向锁默认启用。
  • 替代品 :volatile(仅可见性)、Lock 接口(可重入、可公平)、JUC 包(高级并发工具)。
相关推荐
Escalating_xu几秒前
【C 语言】深入理解指针(3):字符指针、数组指针、二维数组传参、函数指针与转移表
java·c语言·开发语言
可涵不会debug14 分钟前
C++ std::bind 实战教程:线程池、回调场景全覆盖
linux·开发语言·c++
不正经的码狗17 分钟前
Java 开发环境搭建与 Eclipse 使用速通教程
java·开发语言·eclipse
用户81818706274618 分钟前
第37章 Java应用在K8s里的经典坑:容器内存/CPU limit与JVM参数不匹配导致的OOMKilled
java·后端
马剑威(威哥爱编程)31 分钟前
【AI全栈后端12-03】Spring Boot 用多模型路由把智能客服成本降下来
java·人工智能·spring boot
阿钱真强道32 分钟前
24 嵌入式操作系统 | 项目启动与环境准备(PyQt5 / Flask 两个“最小可跑“)
开发语言·qt·flask·架构设计·协议契约
霸道流氓气质35 分钟前
AI模型幻觉检测与抑制完全指南:从规则引擎到RAG对比的Java生产级实战
java·开发语言·人工智能
前端 贾公子39 分钟前
LangChain核心组件 == 提示词(Prompts)
开发语言·c#
niucloud-admin1 小时前
JAVA V6 多商户商城 开发文档——手机端前端
java·开发语言·前端
茉莉玫瑰花茶1 小时前
GO [ 并发 ]
开发语言·后端·golang