当前位置:首页 > 工具 > 正文内容

深入理解Synchronized:用法、JVM实现与锁升级机制

访客 工具 2026年6月13日 1

在多线程编程中,synchronized 是一个关键概念,也是面试中的高频考点。无论你处于哪个技术阶段,掌握它都至关重要。本文将从三个维度系统性地剖析 synchronized:使用方式、JVM 底层的运行机制,以及性能优化与锁升级过程。结合代码示例,帮助你建立更深层次的理解。

1. 使用层面的理解

synchronized 本质上是一种互斥锁,确保同一时刻只有一个线程可以执行被保护的代码块。我们可以把它想象成厕所门上唯一的钥匙,一个人拿钥匙进去后可以重复进出(锁的可重入性),但其他人在他出来之前无法进入。

根据用法不同,synchronized 锁定的目标也不同:

  • 普通同步方法:锁住当前对象实例(this)。
  • 静态同步方法:锁住当前类的 Class 对象。
  • 同步代码块:锁住括号内指定的对象。

理解同步与异步的差异有助于把握锁的作用:

  • 同步:任务按顺序交替执行,如先吃饭后看电视。
  • 异步:任务同时进行,如边吃饭边看剧,提升整体效率。

以下代码示例验证了不同锁对象的影响:

public class SyncTest {
    public static void main(String[] args) {
        Service service = new Service();
        new Thread(() -> Service.m1()).start(); // 静态同步方法
        new Thread(() -> Service.m2()).start(); // 静态同步方法
        new Thread(() -> service.m3()).start(); // 普通同步方法
        new Thread(() -> service.m4()).start(); // 同步代码块
    }

    private static class Service {
        public synchronized static void m1() {
            System.out.println("m1 get lock");
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("m1 release lock");
        }

        public synchronized static void m2() {
            System.out.println("m2 get lock");
            System.out.println("m2 release lock");
        }

        public synchronized void m3() {
            System.out.println("m3 get lock");
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("m3 release lock");
        }

        public void m4() {
            synchronized (this) {
                System.out.println("m4 get lock");
                System.out.println("m4 release lock");
            }
        }
    }
}

运行上述代码会发现:m1 和 m2 互斥(锁住 Class 对象),m3 和 m4 互斥(锁住同一个实例对象),但静态方法与普通方法之间不互斥,因为它们锁定的是不同的对象。

那么锁信息存储在何处?答案在对象头中。一个 Java 对象由三部分组成:对象头(Object Header)、实例数据和对齐填充。对象头中的 Mark Word 存储了哈希值、GC 信息、锁状态等。在 64 位 JVM 中,Mark Word 占 8 字节。我们可以通过 jol-core 工具来观察:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>
public class ObjectMemory {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new Fruit()).toPrintable());
    }
}
class Fruit {
    private boolean flag;
}

输出显示:对象头 12 字节(Mark Word + 类指针) + 实例数据 1 字节 + 对齐填充 3 字节,总共 16 字节。

2. JVM 层面的实现

以简单的同步代码块为例:

public class SyncJvmTest {
    public static void main(String[] args) {
        synchronized (SyncJvmTest.class) {
            System.out.println("jvm sync test");
        }
    }
}

通过 javap -v 反编译后,可以看到 monitorentermonitorexit 两条指令。JVM 规范规定:

  • 每个对象都关联一个 monitor(C++ 实现,称为 ObjectMonitor)。
  • 线程进入同步块时尝试获取 monitor,成功后将其计数器加 1。
  • 如果线程重复进入(即可重入),计数器再次加 1。
  • 其他线程竞争时,会被阻塞在等待队列中,直到持有线程将计数器置 0(释放锁)。

早期的 synchronized 属于重量级锁,因为线程阻塞/唤醒涉及操作系统用户态与内核态切换,开销大。JDK 1.6 之后引入了一系列优化,使其性能大幅提升。

3. 优化与锁升级

JDK 1.6 对 synchronized 进行了重大改进,引入了偏向锁、轻量级锁,并与重量级锁配合,形成一个逐步升级的过程。锁只能升级不能降级。各个状态的 Mark Word 标记如下:

锁状态Mark Word 标志位说明
无锁001对象未被锁定
偏向锁101偏向一个线程,避免 CAS 操作
轻量级锁00多线程交替执行,使用 CAS 自旋
重量级锁10多线程竞争激烈,依赖操作系统互斥量

下面通过代码验证锁状态的变化,使用 jol 工具查看 Mark Word:

public class MarkWordTest {
    private static Fruit fruit = new Fruit();

    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        Thread threadA = new Thread(task);
        Thread threadB = new Thread(task);
        Thread threadC = new Thread(task);
        threadA.start();
        threadA.join(); // 保证 A 先执行
        threadB.start();
        // 模拟不同场景
    }

    private static class Task extends Thread {
        @Override
        public void run() {
            synchronized (fruit) {
                System.out.println("Thread " + Thread.currentThread().getId() + " running");
                try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(ClassLayout.parseInstance(fruit).toPrintable());
            }
        }
    }
}

偏向锁:只启动一个线程时,Mark Word 最后三位的二进制显示为 101。偏向锁记录线程 ID,下次该线程再进入时无需任何同步操作,减少了开销。

轻量级锁:让 A 线程执行完毕后,再启动 B 线程(交替执行)。输出显示锁状态变为 00,表示升级为轻量级锁。此时线程会通过 CAS 自旋尝试获取锁,避免进入内核态阻塞。

重量级锁:启动 A、B、C 三个线程,且让它们几乎同时执行。输出显示锁状态变为 10。当多个线程同时竞争(cas 自旋超过 10 次或第三个线程加入),锁会膨胀为重量级锁,线程挂起并等待操作系统调度。

值得注意的是,重量级锁一旦升级,不可降级。JDK 1.8 中 ConcurrentHashMap 重新采用 synchronized 而非 ReentrantLock,这也体现了对 synchronized 优化成果的认可。

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

深入解析Node.js运行环境与异步I/O架构

深入解析Node.js运行环境与异步I/O架构

核心定义与价值Node.js本质上是一个JavaScript运行环境,而非编程语言或应用框架。它赋予了JavaScript脱离浏览器在服务端、命令行工具及网络应用中执行的能力。其核心意义在于:用单一语言打通前后端开发壁垒。基于事件驱动与非阻塞I/O的架构特性,Node.js在处理API网关、实时通信及微服务等I/O密集型场景时表现卓越,已成为现代后端工程的主流选择。浏览器沙箱限制1995年Java...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。