首页 > Java > java教程 > 正文

synchronized 关键字的实现原理是什么?它是如何保证线程安全的?

betcha
发布: 2025-09-04 16:06:01
原创
458人浏览过
synchronized 是 Java 中保证线程安全的核心机制,其本质是通过 JVM 内置的 Monitor(监视器)实现互斥访问。当多个线程竞争同步资源时,synchronized 依靠对象头中的 Mark Word 和锁升级机制(偏向锁 → 轻量级锁 → 重量级锁)动态调整锁的实现方式,以平衡性能与线程安全。在字节码层面,synchronized 代码块通过 monitorenter 和 monitorexit 指令获取和释放锁,而 synchronized 方法则通过 ACC_SYNCHRONIZED 标志隐式加锁。除了互斥性,synchronized 还通过“happens-before”原则保证内存可见性:释放锁时将工作内存的修改刷新到主内存,获取锁时使本地缓存失效并重新读取主内存数据,从而确保线程间共享变量的最新值可见。常见使用场景包括保护共享资源、保证复合操作的原子性、实现单例模式的双重检查锁定以及配合 wait/notify 实现线程通信。性能方面,JVM 对低竞争场景下的偏向锁和轻量级锁优化显著,但在高竞争环境下可能因重量级锁导致线程阻塞和上下文切换开销增大,影响吞吐量。因此,应尽量减小锁粒度、避免死锁,并在高并发场景下权衡使用 ReentrantLock

synchronized 关键字的实现原理是什么?它是如何保证线程安全的?

synchronized
登录后复制
关键字在 Java 中,在我看来,它本质上就是一把“锁”,一把确保同一时间只有一个线程能够访问特定代码区域或对象的锁。它通过 JVM 层面内置的监视器(Monitor)机制来实现互斥访问,同时,它还巧妙地保证了内存可见性,确保一个线程对共享变量的修改能被其他线程及时看到,从而有效避免了多线程环境下的数据不一致问题,是保证线程安全最直接、最基础的手段之一。

解决方案

synchronized
登录后复制
关键字的实现原理,说白了,就是围绕着 Java 对象头里的一个特殊结构——Monitor(监视器)来展开的。当我们使用
synchronized
登录后复制
关键字修饰一个代码块或者一个方法时,实际上就是请求获取这个 Monitor 的所有权。

具体来说:

  • synchronized
    登录后复制
    代码块:
    当我们写
    synchronized (this)
    登录后复制
    synchronized (anObject)
    登录后复制
    时,JVM 会在编译时生成
    monitorenter
    登录后复制
    monitorexit
    登录后复制
    这两个字节码指令。
    • monitorenter
      登录后复制
      指令:它尝试获取指定对象的 Monitor 锁。如果对象的 Monitor 计数器为 0,表示没有线程持有该锁,当前线程就能成功获取,然后将计数器加 1,并把 Monitor 的所有者设置为当前线程。如果计数器不为 0,说明有其他线程持有锁,当前线程就会被阻塞,直到持有锁的线程释放。
    • monitorexit
      登录后复制
      指令:它会释放 Monitor 锁,将计数器减 1。当计数器减到 0 时,表示锁完全释放,其他等待的线程就有机会获取锁。值得注意的是,为了防止异常情况下锁无法释放,JVM 会在
      monitorenter
      登录后复制
      后面自动生成两个
      monitorexit
      登录后复制
      指令,一个在正常执行路径上,一个在异常处理路径上。
  • synchronized
    登录后复制
    方法:
    对于
    synchronized
    登录后复制
    修饰的实例方法或静态方法,JVM 不会显式地使用
    monitorenter
    登录后复制
    monitorexit
    登录后复制
    指令。相反,它会在方法对应的
    Constant Pool
    登录后复制
    中设置一个
    ACC_SYNCHRONIZED
    登录后复制
    标志。当方法被调用时,JVM 会检查这个标志。如果设置了,执行线程就会自动尝试获取方法所属对象的 Monitor 锁(对于实例方法是实例对象,对于静态方法是类的 Class 对象)。方法执行完毕后,无论正常返回还是抛出异常,锁都会被自动释放。

无论是哪种形式,核心都是通过 Monitor 实现互斥。一个 Monitor 只能被一个线程持有,这确保了被

synchronized
登录后复制
保护的代码块在任何时刻都只有一个线程在执行,从而防止了竞态条件(Race Condition)的发生。

synchronized
登录后复制
关键字在 JVM 层面是如何具体实现的?

在我看来,

synchronized
登录后复制
的实现远不止
monitorenter
登录后复制
monitorexit
登录后复制
那么简单,这背后其实藏着 JVM 对性能的极致优化和对并发编程复杂性的深刻理解。它的具体实现,与 Java 对象头中的
Mark Word
登录后复制
息息相关。

Java 对象的内存布局通常包括对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。对象头又分为两部分:

Mark Word
登录后复制
Klass Pointer
登录后复制
。我们关注的重点是
Mark Word
登录后复制
,它存储了对象的哈希码、GC 信息以及最重要的——锁信息。

JVM 为了提高

synchronized
登录后复制
的性能,引入了锁升级(Lock Escalation)机制,主要经历了以下几个阶段:

  1. 偏向锁(Biased Locking): 这是 JVM 默认开启的一种优化。当一个线程第一次访问同步块并获取锁时,JVM 会在
    Mark Word
    登录后复制
    中记录下这个线程的 ID。如果后续该线程再次进入同步块,无需再进行任何同步操作,直接就可以执行。这就像给对象贴了个“专属标签”,只有你一个人用,就不用每次都检查门锁了。只有当有另一个线程尝试获取这个锁时,偏向锁才会撤销。
  2. 轻量级锁(Lightweight Locking): 当偏向锁被撤销时,或者一开始就有多个线程竞争锁,但竞争不激烈(即没有线程阻塞),JVM 会升级为轻量级锁。线程会在自己的栈帧中创建一个
    Lock Record
    登录后复制
    ,然后尝试使用 CAS(Compare And Swap)操作将对象的
    Mark Word
    登录后复制
    替换为指向
    Lock Record
    登录后复制
    的指针。如果成功,表示获取锁;如果失败,说明有其他线程也尝试获取,此时会膨胀为重量级锁。轻量级锁的优点是避免了操作系统级别的线程上下文切换,开销较小。
  3. 重量级锁(Heavyweight Locking): 当多个线程竞争激烈,或者轻量级锁 CAS 失败时,锁就会膨胀为重量级锁。此时,
    Mark Word
    登录后复制
    会指向一个真正的
    Monitor
    登录后复制
    对象(通常是 C++ 实现的
    ObjectMonitor
    登录后复制
    ),这个 Monitor 是在操作系统层面实现的。线程会被阻塞并挂起,进入等待队列,直到持有锁的线程释放锁。重量级锁的开销最大,因为它涉及到用户态到内核态的切换,以及线程的调度和上下文切换。

所以,

synchronized
登录后复制
的实现原理,其实是一个动态调整的过程,JVM 会根据实际的竞争情况,在偏向锁、轻量级锁和重量级锁之间进行切换,力求在保证线程安全的前提下,最大化程序的性能。这真的是一个很精妙的设计。

除了互斥,
synchronized
登录后复制
如何保证内存可见性?

很多人提到

synchronized
登录后复制
,首先想到的是它的互斥性,也就是“同一时间只有一个线程能访问”。但说实话,它的内存可见性保证同样重要,甚至在某些场景下更为关键。在并发编程中,内存可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。如果缺乏这个保证,即使有互斥,也可能因为线程读取到旧值而导致逻辑错误。

阿里云-虚拟数字人
阿里云-虚拟数字人

阿里云-虚拟数字人是什么? ...

阿里云-虚拟数字人 2
查看详情 阿里云-虚拟数字人

synchronized
登录后复制
关键字通过 Java 内存模型(JMM)定义的“happens-before”原则来保证内存可见性。简单来说,
synchronized
登录后复制
块的解锁操作
happens-before
登录后复制
于后续对同一个
synchronized
登录后复制
块的加锁操作。

具体机制是这样的:

  • 当一个线程释放
    synchronized
    登录后复制
    锁时:
    它会将自己在工作内存(线程私有缓存)中对所有共享变量的修改,全部刷新(flush)到主内存中。这就像是线程在离开一个共享工作区时,会把所有自己修改过的文件都保存到公共服务器上。
  • 当一个线程获取
    synchronized
    登录后复制
    锁时:
    它会强制性地使自己的工作内存中所有共享变量的缓存失效,然后从主内存中重新读取这些共享变量的最新值。这就像是线程进入共享工作区时,会先清空自己本地的旧文件,然后从公共服务器上下载最新的版本。

通过这种“先写回主内存,再从主内存读取”的机制,

synchronized
登录后复制
确保了在一个线程执行完同步块并释放锁之后,其对共享变量的修改对后续获取相同锁的线程是可见的。这样,即使多个线程在不同的 CPU 核心上运行,也能保证它们看到的是共享变量的最新状态,从而避免了缓存不一致导致的可见性问题。

synchronized
登录后复制
关键字有哪些使用场景和性能考量?

synchronized
登录后复制
作为一个 JVM 内置的同步机制,在 Java 并发编程中有着不可替代的地位。了解它的使用场景和性能考量,能帮助我们更好地利用它。

使用场景:

  1. 保护共享资源: 这是
    synchronized
    登录后复制
    最经典、最主要的应用。任何时候,只要有多个线程需要同时访问并修改一个共享变量、共享对象或数据结构(如
    ArrayList
    登录后复制
    HashMap
    登录后复制
    等非线程安全的集合),都应该使用
    synchronized
    登录后复制
    来保护这些操作,以防止竞态条件导致的数据损坏或不一致。
    class Counter {
        private int count = 0;
        public synchronized void increment() {
            count++;
        }
        public synchronized int getCount() {
            return count;
        }
    }
    登录后复制
  2. 保证方法执行的原子性: 有些业务逻辑,比如转账操作,需要一系列步骤(扣钱、加钱)作为一个不可分割的整体执行。
    synchronized
    登录后复制
    可以确保这些步骤要么全部完成,要么全部不完成,中间不会被其他线程打断。
  3. 单例模式的懒汉式初始化: 在实现懒汉式单例模式时,为了保证
    instance
    登录后复制
    变量只被初始化一次,通常会使用
    synchronized
    登录后复制
    进行双重检查锁定(Double-Checked Locking)。
    public class Singleton {
        private volatile static Singleton instance; // volatile 保证可见性和禁止指令重排
        private Singleton() {}
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) { // 锁住 Class 对象
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    登录后复制
  4. 线程间通信: 虽然
    wait()
    登录后复制
    ,
    notify()
    登录后复制
    ,
    notifyAll()
    登录后复制
    方法不是
    synchronized
    登录后复制
    本身的功能,但它们必须在
    synchronized
    登录后复制
    块或方法内部调用,因为它们依赖于 Monitor 机制来管理线程的等待和唤醒。

性能考量:

synchronized
登录后复制
的性能,尤其是重量级锁,确实是我们需要关注的。

  1. 锁粒度: 锁的粒度越小,并发度越高。如果同步块包含了大量与共享资源无关的代码,那么就会不必要地阻塞其他线程,降低性能。所以,应该尽可能地缩小同步代码块的范围,只保护真正需要同步的部分。
  2. 锁竞争:
    • 低竞争: 在低竞争场景下,由于 JVM 的偏向锁和轻量级锁优化,
      synchronized
      登录后复制
      的性能通常非常好,甚至可能比
      java.util.concurrent.locks.ReentrantLock
      登录后复制
      还要好,因为它避免了
      ReentrantLock
      登录后复制
      内部 CAS 操作的开销。
    • 高竞争: 当多个线程频繁地竞争同一个锁时,锁会升级为重量级锁。此时,线程的阻塞和唤醒会涉及到操作系统层面的上下文切换,这会带来显著的性能开销。在高并发、高竞争的场景下,
      synchronized
      登录后复制
      可能会成为性能瓶颈。
  3. 死锁: 不恰当的锁顺序或嵌套锁可能导致死锁。一旦发生死锁,程序就会停滞不前,这是并发编程中最棘手的问题之一。
  4. 可伸缩性:
    synchronized
    登录后复制
    是一种独占锁,它限制了并发度。在高并发系统中,如果同步块成为瓶颈,可能会限制系统的整体吞吐量和可伸缩性。

总的来说,

synchronized
登录后复制
是一个强大且易于使用的线程安全工具。在大多数中低并发场景下,它的性能表现是完全可以接受的,并且由于 JVM 的优化,很多时候甚至比手动实现的锁更高效。但在面对极高并发和复杂同步需求时,我们可能需要考虑
java.util.concurrent
登录后复制
包下更灵活、功能更丰富的工具,比如
ReentrantLock
登录后复制
StampedLock
登录后复制
等,它们提供了更细粒度的控制和更高级的特性,比如公平锁、非阻塞尝试获取锁等。但无论如何,理解
synchronized
登录后复制
的工作原理,都是我们掌握 Java 并发编程的基石。

以上就是synchronized 关键字的实现原理是什么?它是如何保证线程安全的?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号