深入理解synchronized

发布于:2025-04-19 ⋅ 阅读:(23) ⋅ 点赞:(0)

synchronized 是 Java 中实现线程同步的核心机制,其底层实现依赖于 JVM 的监视器(Monitor)和对象头(Object Header)结构。以下是其实现机制的详细解析:


一、核心实现原理

  1. Monitor 监视器机制
    • 每个 Java 对象关联一个 Monitor(监视器),用于控制线程对临界区的访问。
    • Monitor 通过 monitorentermonitorexit 指令实现锁的获取与释放。当线程进入同步代码块时,会尝试获取对象的 Monitor;若 Monitor 被其他线程持有,则当前线程进入阻塞状态。

  2. 对象头与锁状态
    • 对象头包含 Mark Word(标记字段),存储锁状态、哈希码、GC分代年龄、线程持有的锁、偏向线程 ID、偏向时间戳等信息。
    • 锁状态通过 Mark Word 中的标记位动态切换,支持无锁、偏向锁、轻量级锁、重量级锁四种模式。


二、锁升级机制(JDK 1.6+)

JVM 通过锁升级优化性能,根据竞争程度动态调整锁策略:

1. 无锁(Non-locked)

状态标识:Mark Word 中锁标志位为 01,偏向锁标志位为 0
适用场景:对象刚创建时,或无线程竞争的长期无锁状态。
实现细节
• 线程通过 CAS 操作直接修改对象头 Mark Word 获取锁。
• 无额外同步开销,适用于单线程访问场景。


2. 偏向锁(Biased Locking)

状态标识:Mark Word 中锁标志位为 01,偏向锁标志位为 1
核心机制
首次加锁:线程首次进入同步块时,JVM 通过 CAS 将线程 ID 写入 Mark Word,标记为偏向锁。
重复访问:同一线程再次进入时,直接比对 Mark Word 中的线程 ID,无需同步操作。
撤销条件:当其他线程尝试竞争时,触发偏向锁撤销(需全局安全点暂停线程),升级为轻量级锁。
优化点
• 减少无竞争场景的同步开销(如单线程循环内多次加锁)。
• JDK 15+ 默认禁用偏向锁(通过 -XX:-UseBiasedLocking 可手动启用)。


3. 轻量级锁(Lightweight Locking)

状态标识:Mark Word 中锁标志位为 00,指向线程栈中的锁记录(Lock Record)。
核心机制
加锁:线程在栈帧中创建 Lock Record,通过 CAS 将 Mark Word 指向该记录。
自旋锁(Spin Lock)
实现:CAS 失败时,线程在用户态循环尝试获取锁(默认自旋 10 次,JDK 12+ 支持自适应自旋)。
优势:避免线程阻塞和上下文切换,适用于短暂竞争场景。
限制:自旋时间过长会浪费 CPU 资源,需结合自适应策略动态调整。
升级条件:自旋失败次数超过阈值或竞争激烈时,升级为重量级锁。


4. 重量级锁(Heavyweight Locking)

状态标识:Mark Word 中锁标志位为 10,指向 ObjectMonitor 监视器。
核心机制
阻塞等待:线程通过操作系统互斥量(Mutex)进入阻塞状态,由 OS 调度唤醒。
队列管理:使用 EntryList(阻塞队列)和 WaitSet(等待队列)管理线程状态。
性能影响:上下文切换开销大,适用于高竞争或长时间持有锁的场景。
优化点
• 锁消除(JIT 编译器移除无竞争锁)。
• 锁粗化(合并连续锁操作减少开销)。


三、锁的存储与同步范围

  1. 同步范围
    实例方法:锁对象为当前实例(this)。
    静态方法:锁对象为类的 Class 对象(如 String.class)。
    代码块:锁对象由 synchronized(obj) 显式指定。

  2. 线程状态流转
    Contention List:所有请求锁的线程初始队列。
    Entry List:竞争候选线程队列(优先级较高的线程)。
    OnDeck:当前竞争锁的线程(单线程竞争)。
    Owner:持有锁的线程。
    Wait Set:通过 wait() 进入等待的线程队列。


四、关键特性

  1. 可重入性(Reentrancy)
    • 同一线程可多次获取同一把锁(如递归方法),避免死锁。

  2. 线程阻塞与唤醒
    • 通过 wait()/notify()/notifyAll() 实现线程间协作,需配合 synchronized 使用。

  3. JVM 优化措施
    锁消除:编译器消除无竞争场景的锁。
    锁粗化:合并连续的锁操作以减少开销。

  4. 不可逆性
    • 锁状态只能从无锁 → 偏向锁 → 轻量级锁 → 重量级锁单向升级,无法降级。


五、与 Lock 的对比

特性 synchronized Lock 接口
实现层级 JVM 内置锁 需手动实现(如 ReentrantLock
性能 低竞争时高效,高竞争依赖 OS 自旋锁、公平锁等灵活策略
中断响应 不支持中断等待 支持 lockInterruptibly()
条件变量 依赖 wait()/notify() 通过 Condition 精细化控制

六、使用建议

  1. 优先使用轻量级锁:通过减少锁粒度、避免长时间持有锁优化性能。
  2. 避免嵌套锁:减少死锁风险。
  3. 监控锁竞争:通过 JVM 工具(如 jstack)分析锁阻塞问题。

通过理解其底层机制和优化策略,可以更高效地设计并发程序。


网站公告

今日签到

点亮在社区的每一天
去签到