type
status
date
slug
summary
tags
category
icon
password

引言

在多线程编程中,线程同步是一个至关重要的概念。Java 提供了多种实现线程同步的机制,其中最常用的之一就是 synchronized 关键字。本文将详细介绍 synchronized 的实现原理,并讨论在实际应用中如何进行锁优化,以提高程序的执行效率。

synchronized 的实现原理

synchronized 是 Java 提供的一种内置锁机制,用于确保在同一时刻只有一个线程可以执行同步代码块。它可以用来修饰方法或者代码块,实现对资源的互斥访问。

1. synchronized 方法和同步代码块

synchronized 关键字可以用在方法上,也可以用在代码块上。
  • 同步方法:synchronized 可以修饰实例方法和静态方法。对于实例方法,锁是加在实例对象上的;对于静态方法,锁是加在类对象上的。
    • 同步代码块:synchronized 还可以用来修饰代码块,这样可以更加精细地控制锁的粒度。

      2. Monitor 和锁

      synchronized 的底层是基于 Monitor 实现的。Monitor 是一种同步工具,它在 Java 中通过对象的 Mark Word 以及 Monitor 对象来实现。每个 Java 对象都有一个关联的 Monitor 对象,当一个线程进入同步块时,它会尝试获取该对象的 Monitor,如果获取成功,则进入同步块执行;否则,线程会被阻塞,直到 Monitor 被释放。

      3. JVM 实现

      在 JVM 中,synchronized 的实现是通过字节码指令来完成的。主要涉及两条指令:
      • monitorenter:当线程进入同步块时,执行该指令。
      • monitorexit:当线程退出同步块时,执行该指令。
      当编译 Java 代码时,编译器会在同步方法或同步代码块的前后插入 monitorentermonitorexit 指令。

      4. Mark Word 和对象头

      每个 Java 对象都有一个对象头,其中包含 Mark Word 和类型指针。Mark Word 中存储了对象的哈希码、GC 年龄、锁标志位等信息。当对象被锁定时,Mark Word 会保存锁的状态和持有锁的线程信息。

      锁优化

      为了提高程序的性能,Java 对锁进行了多种优化,包括偏向锁、轻量级锁和重量级锁。锁优化的目标是尽量减少线程同步带来的性能开销。

      1. 偏向锁

      偏向锁是 Java 6 引入的一种锁优化机制,旨在减少无竞争情况下的同步开销。当一个线程首次获取锁时,锁会偏向该线程,Mark Word 中会记录偏向线程的 ID。在之后的执行中,如果该线程再次请求锁,就不需要再进行 CAS 操作,从而减少了锁获取的开销。
      偏向锁的获取过程:
      • 首次获取锁时,将 Mark Word 的锁标志位设置为偏向模式,并记录偏向线程 ID。
      • 之后相同线程获取锁时,直接通过检查 Mark Word 是否偏向当前线程来决定是否可以进入同步块。
      偏向锁的撤销:
      • 当另一个线程请求偏向锁时,锁会被撤销,并升级为轻量级锁。
      偏向锁适用于无锁竞争的场景,但在高竞争场景下会带来额外的开销。在高竞争场景中,多个线程会同时争夺同一个锁资源,这种情况下,偏向锁容易被频繁撤销(撤销过程涉及到安全点暂停线程和锁膨胀),这会导致性能下降,因此,可以通过 JVM 参数 -XX:-UseBiasedLocking 来关闭偏向锁。

      2. 轻量级锁

      轻量级锁也是 Java 6 引入的一种锁优化机制,旨在减少竞争不激烈情况下的同步开销。当一个线程尝试获取一个没有被锁定的锁时,会使用 CAS 操作将对象的 Mark Word 替换为指向栈中锁记录的指针。
      轻量级锁的获取过程:
      • 线程在进入同步块时,会首先检查对象的 Mark Word 是否处于无锁状态。
      • 如果是,则使用 CAS 操作尝试将 Mark Word 替换为指向栈中锁记录的指针。
      • 如果 CAS 操作成功,则表示获取了轻量级锁。
      轻量级锁的释放:
      • 线程在退出同步块时,会将 Mark Word 恢复为无锁状态,或者指向下一个锁记录。
      轻量级锁的适用场景是锁竞争不激烈的情况,如果有多个线程竞争同一个锁,轻量级锁会膨胀为重量级锁。

      3. 重量级锁

      当偏向锁和轻量级锁都无法满足需求时,锁会膨胀为重量级锁。重量级锁使用操作系统的互斥量来实现,线程在获取锁失败后会被阻塞,直到锁被释放。
      重量级锁的获取过程:
      • 线程在进入同步块时,首先尝试使用 CAS 操作获取锁。
      • 如果失败,则进入阻塞状态,并加入到锁的等待队列中。
      重量级锁的释放:
      • 线程在退出同步块时,会唤醒等待队列中的一个线程。
      重量级锁适用于高竞争的场景,但由于涉及操作系统的线程调度,其性能开销较大。

      锁优化的实践

      在实际应用中,合理使用和优化锁可以显著提高程序的并发性能。以下是一些常见的锁优化实践。

      1. 锁分解

      锁分解是指将一个大锁分解为多个小锁,从而减少锁的竞争。例如,在一个有多个独立资源的类中,可以为每个资源分别使用一个锁,而不是为整个类使用一个锁。

      2. 锁粗化

      锁粗化是指将多个连续的锁操作合并为一个锁操作,从而减少锁的获取和释放次数。例如,在一个循环中多次获取和释放同一个锁,可以将锁操作移到循环外部。
      可以优化为:

      3. 读写锁

      读写锁是一种特殊的锁,它允许多个读线程同时访问资源,但在写线程访问资源时,所有读线程和其他写线程都被阻塞。Java 提供了 ReentrantReadWriteLock 类来实现读写锁。

      4. 乐观锁

      乐观锁是一种假设不会发生冲突的锁机制,通过版本号或时间戳来检测数据是否被修改。Java 中的 Atomic 类和 java.util.concurrent 包中的一些类使用了乐观锁机制。

      总结

      synchronized 作为 Java 提供的一种内置锁机制,通过 Monitor 实现线程同步,保证了资源的互斥访问。为了提高性能,Java 引入了偏向锁、轻量级锁和重量级锁,并通过锁分解、锁粗化、读写锁和乐观锁等优化策略,进一步减少锁竞争带来的开销。
      在实际开发中,合理使用锁优化技术,可以显著提高程序的并发性能,降低锁竞争对系统资源的消耗。理解 synchronized 的实现原理和锁优化技术,对于编写高效的多线程程序具有重要意义。希望本文对你理解和应用 synchronized 及锁优化有所帮助。
      DDD是什么鬼?Doris: 数据分析与处理的新利器
      Loading...
      奥利弗
      奥利弗
      巴塔哥尼亚的门徒
      最新发布
      🎨 一键转换,让你的 SVG 飞起来!——介绍「SVG 魔法转换器」
      2025-4-30
      🚀 告别繁琐,实时掌握币圈脉搏!全新加密货币实时行情追踪神器上线!
      2025-4-28
      厌倦了千篇一律的鸡汤?来点“毒”的,再加点暖和和疯狂星期四的快乐!
      2025-4-28
      用呼吸找回内心的平静:一款简单有效的在线冥想工具
      2025-4-23
      谁在剥夺骑手的自由?——从“外卖平台二选一”事件看平台责任与底层困局
      2025-4-21
      手把手教你制作吉卜力风格的微信表情包!
      2025-4-17
      公告
       
      世界和平!