java synchronized关键字用法和底层原理举例详解

 更新时间:2026年01月30日 08:19:44   作者:看透也说透kevin  
synchronized关键字是java语言进行多线程编程,非常常用的关键,这篇文章主要介绍了java synchronized关键字用法和底层原理的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一、synchronized的用法

synchronized 关键字用于实现线程同步,确保多个线程在访问共享资源时不会发生数据竞争和不一致的问题。它主要有三种使用方式:

1. 同步实例方法

public synchronized void method() {
    // 同步代码
}
  • 锁对象是当前实例(this)。
  • 同一个实例的多个线程调用此方法时会互斥。

2. 同步静态方法

public static synchronized void staticMethod() {
    // 同步代码
}
  • 锁对象是当前类的 Class 对象(如 MyClass.class)。
  • 所有实例的线程调用此方法时都会互斥。

3. 同步代码块

synchronized (lockObject) {
    // 同步代码
}
  • 可以灵活指定锁对象(可以是任意对象)。
  • 缩小了同步范围,提高性能。

4. 深入:wait(),notify(),notifyAll()

这三个方法是定义在 Object 类中的本地方法,必须在一个同步代码块或同步方法内(即已经持有该对象的监视器锁)调用,否则会抛出 IllegalMonitorStateException 异常。它们与 synchronized 配合使用,实现线程间的协调(等待/通知机制)。

  • wait(): 使当前线程释放其持有的对象锁,并进入等待状态(WAITING),直到其他线程调用该对象的 notify()notifyAll() 方法,或被中断。调用后,线程会释放锁
  • notify(): 随机唤醒一个正在等待该对象锁的线程。被唤醒的线程会从等待池移动到阻塞队列(EntryList)中,等待锁释放后重新竞争锁。
  • notifyAll(): 唤醒所有正在等待该对象锁的线程。这些线程都会被移动到阻塞队列中,共同竞争锁。

典型的生产者-消费者模式示例:

public class WaitNotifyExample {
    private final Object lock = new Object();
    private boolean conditionMet = false;

    public void consumer() throws InterruptedException {
        synchronized (lock) {
            // 使用while循环防止"虚假唤醒"
            while (!conditionMet) {
                lock.wait(); // 释放lock锁,当前线程进入等待
            }
            // 条件满足,执行消费任务
            System.out.println("Consuming...");
            conditionMet = false;
            lock.notifyAll(); // 消费完成,通知生产者
        }
    }

    public void producer() throws InterruptedException {
        synchronized (lock) {
            while (conditionMet) {
                lock.wait(); // 如果条件已满足,则等待消费者消费
            }
            // 生产任务
            System.out.println("Producing...");
            conditionMet = true;
            lock.notifyAll(); // 生产完成,通知消费者
        }
    }
}

关键点:

  1. 总是需要在循环中调用 wait() 以防止虚假唤醒(Spurious Wakeup),即线程可能在没有被通知、中断或超时的情况下醒来。
  2. wait() 会释放锁,而 sleep() 不会。
  3. 通常更推荐使用 notifyAll(),因为 notify() 是随机唤醒,可能导致某些线程永远无法被唤醒(线程饥饿)。

二、底层原理

synchronized 的底层实现依赖于 JVM 内部的 监视器锁(Monitor) 机制,主要涉及以下概念:

1. 对象头与 Mark Word

  • 在 HotSpot 虚拟机中,每个对象都有一个对象头,其中包含 Mark Word(标记字段)。
  • Mark Word 的长度为 32位 或 64位,用于存储对象的哈希码、分代年龄、锁状态标志位等信息。锁的状态就记录在这里。

2. 锁升级的详细过程

为了平衡性能与安全性,JVM 对 synchronized 进行了优化,引入了锁升级机制。这个过程是单向的:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

  1. 无锁状态 (01)

    • 初始状态,对象刚被创建,尚未有任何线程竞争。
  2. 偏向锁 (Biased Locking) (01)

    • 目的:消除在无竞争情况下的同步开销。假设在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。
    • 流程
      • 加锁:当一个线程第一次访问同步块时,它会通过 CAS 操作将自己的 线程ID 写入对象头的 Mark Word。如果成功,则进入偏向模式,锁标志位变为 01,且偏向模式标志为 1
      • 执行:此后,只要这个线程再次进入同步块,只需检查 Mark Word 中的线程ID是否是自己。如果是,则无需任何同步操作(如CAS、系统互斥)直接执行,极大提升性能。
      • 撤销:一旦出现另一个线程来尝试竞争锁,偏向锁就会撤销(Revoke Bias)。撤销过程需要等待全局安全点(STW),然后检查原持有偏向锁的线程是否仍活跃。如果活跃,则锁升级为轻量级锁;如果已不活跃,则可将对象置为无锁状态,重新偏向新的线程。
  3. 轻量级锁 (Lightweight Locking) (00)

    • 目的:在有多线程竞争,但竞争时间错开(即近乎交替执行)的场景下,避免线程直接进入阻塞,减少用户态到内核态的切换开销。
    • 加锁流程
      • 线程在执行同步块之前,JVM 会先在当前线程的栈帧中创建一个名为锁记录(Lock Record) 的空间。
      • 将对象头的 Mark Word 复制到线程的锁记录中(称为 Displaced Mark Word)。
      • 线程尝试通过 CAS 操作将对象头的 Mark Word 替换为指向其锁记录的指针。
      • 如果成功,当前线程获得锁,锁标志位变为 00
      • 如果失败,表示至少有一条线程与当前线程竞争,当前线程会尝试自旋(循环重试CAS操作)来获取锁。
    • 解锁流程
      • 使用 CAS 操作将 Displaced Mark Word 替换回对象头。
      • 如果成功,表示没有竞争发生。
      • 如果失败,表示当前锁存在竞争,锁会膨胀为重量级锁,并在解锁时唤醒等待的线程。
    • 自旋优化:线程不会立即阻塞,而是循环尝试获取锁。避免了线程上下文切换的开销,但会消耗CPU。如果自旋过度(如自旋次数超过阈值,或竞争的线程数过多),锁会直接升级为重量级锁。
  4. 重量级锁 (Heavyweight Locking) (10)

    • 目的:处理高并发、激烈竞争的场景。
    • 流程:当轻量级锁竞争失败后,会膨胀为重量级锁。此时,Mark Word 中存储的是指向一个互斥量(Mutex) 的指针,这个互斥量由操作系统内核提供。
    • 所有等待锁的线程都会进入阻塞状态(BLOCKED),不再消耗CPU。锁的获取和释放需要操作系统进行线程的挂起唤醒,这个过程涉及到复杂的用户态到内核态的切换,开销最大。

锁升级状态总结表:

锁状态存储内容标志位适用场景
无锁对象的哈希码、分代年龄等01无竞争
偏向锁持有偏向锁的线程ID、Epoch、分代年龄、偏向模式标志 101只有一个线程访问同步块
轻量级锁指向栈中锁记录的指针00多个线程交替访问,低并发竞争
重量级锁指向互斥量(Mutex)的指针10高并发激烈竞争

3. 底层指令

  • 同步代码块通过 monitorentermonitorexit 指令实现:
    • 进入同步块时执行 monitorenter,退出时执行 monitorexit(包括正常退出和异常退出)。
  • 同步方法通过方法表中的 ACC_SYNCHRONIZED 标志标识,JVM通过此标志判断是否需要同步。

三、注意事项

  1. 可重入性synchronized 是可重入锁,同一线程可多次获取同一把锁。
  2. 锁对象不能为 null:同步代码块的锁对象不能是 null,否则会抛出 NullPointerException
  3. 性能考虑
    • 尽量减小同步范围(使用代码块而非整个方法)。
    • 避免锁竞争激烈,否则会升级为重量级锁,降低性能。
  4. 与 Lock 的区别
    • synchronized 是关键字,JVM 级别实现;Lock 是 API 级别实现。
    • Lock 更灵活(可中断、超时、公平锁等),但需手动释放锁。
  5. wait/notify 使用注意:
    • 必须在同步块内调用。
    • 使用 while 循环检查条件,防止虚假唤醒。
    • 明确锁对象,调用 obj.wait() 时必须持有 obj 的锁。

四、示例代码

public class SynchronizedExample {
    private final Object lock = new Object();
    private int count = 0;

    // 同步实例方法
    public synchronized void increment() {
        count++;
    }

    // 同步静态方法
    public static synchronized void staticMethod() {
        // ...
    }

    // 同步代码块
    public void doSomething() {
        synchronized (lock) {
            // 同步代码
        }
    }
}

总结

synchronized 是 Java 中最基本的线程同步机制,通过 JVM 的 Monitor 和精细的锁升级策略(偏向锁->轻量级锁->重量级锁)实现了在高性能无竞争场景和高强度竞争场景下的自适应同步。其内置的 wait/notify 机制为线程间协调提供了基础。使用时需注意锁的范围、竞争情况以及 wait/notify 的正确用法,以避免性能问题和逻辑错误。对于更复杂的场景,可以考虑 java.util.concurrent 包中的高级并发工具。

到此这篇关于java synchronized关键字用法和底层原理举例详解的文章就介绍到这了,更多相关java synchronized关键字用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解高性能缓存Caffeine原理及实战

    详解高性能缓存Caffeine原理及实战

    Caffeine是基于Java 8开发的,提供了近乎最佳命中率的高性能本地缓存组件,Spring5开始不再支持Guava Cache,改为使用Caffeine。Caffeine提供的内存缓存使用参考Google guava的API
    2021-06-06
  • 一篇文章带你Java多线程入门

    一篇文章带你Java多线程入门

    这篇文章主要为大家介绍了Java多线程入门,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • java Iterator.remove()实例方法分析

    java Iterator.remove()实例方法分析

    在本篇文章里小编给大家整理了一篇关于java Iterator.remove()实例方法分析,有兴趣的朋友们跟着学习下。
    2021-01-01
  • 解决mybatis批量更新出现SQL报错问题

    解决mybatis批量更新出现SQL报错问题

    这篇文章主要介绍了mybatis批量更新出现SQL报错,解决办法也很简单只需要在application.properties配置文中的数据源url后面添加一个参数,需要的朋友可以参考下
    2022-02-02
  • Java通俗易懂系列设计模式之建造者模式

    Java通俗易懂系列设计模式之建造者模式

    这篇文章主要介绍了Java通俗易懂系列设计模式之建造者模式,对设计模式感兴趣的读者,一定要看一下
    2021-04-04
  • Spring Boot整合Kafka教程详解

    Spring Boot整合Kafka教程详解

    这篇文章主要为大家介绍了Spring Boot整合Kafka教程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Jmeter BeanShell 内置变量vars、props、prev的使用详解

    Jmeter BeanShell 内置变量vars、props、prev的使用详解

    这篇文章主要介绍了Jmeter BeanShell 内置变量vars、props、prev的使用 ,文中给大家介绍了Jmeter中关于BeanShell的相关知识,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Spring中的@Conditional注解实现分析

    Spring中的@Conditional注解实现分析

    这篇文章主要介绍了Spring中的@Conditional注解实现分析,  @Conditional是Spring 4出现的注解,但是真正露出价值的是Spring Boot的扩展@ConditionalOnBean等,需要的朋友可以参考下
    2023-12-12
  • Java基础知识之Java语言概述

    Java基础知识之Java语言概述

    这篇文章主要介绍了Java基础知识之Java语言概述,本文介绍了Java语言相关的基础知识、历史介绍、主要应用方向等内容,需要的朋友可以参考下
    2015-03-03
  • SpringSecurity实现前后端分离的示例详解

    SpringSecurity实现前后端分离的示例详解

    Spring Security默认提供账号密码认证方式,具体实现是在UsernamePasswordAuthenticationFilter 中,这篇文章主要介绍了SpringSecurity实现前后端分离的示例详解,需要的朋友可以参考下
    2023-03-03

最新评论