1. 为什么当初要这样设计?(设计初衷)

Java 诞生于 20 世纪 90 年代中期,当时多线程编程(并发)正在兴起。Java 的设计者高斯林(James Gosling)希望 Java 成为一门简单易用的多线程语言。

简化并发编程

如果对象没有内置锁,你每次想给一段代码加锁,都必须显式地创建一个 Lock 对象:

// 如果没有内置锁,你得这么写:
Lock myLock = new ReentrantLock();
void doSomething() {
    myLock.lock();
    try {
        // 业务逻辑
    } finally {
        myLock.unlock();
    }
}

而 Java 设计者引入了 synchronized 关键字,直接让对象充当锁,把复杂的同步简化成了:

// 极简的同步写法
synchronized(this) {
    // 业务逻辑
}

“万物皆对象,万物皆可为锁”的设计,让开发者不需要管理一堆乱七八糟的锁对象,只要拿到目标对象,就能直接进行同步控制。


2. 这样不浪费内存吗?JVM 是怎么“偷懒”的?

你担心的内存浪费,JVM 工程师早就想到了。他们绝对不会傻到给每个刚 new 出来的对象都分配一个沉重的操作系统级别的锁(Monitor)。

JVM 解决这个问题的核心思路是:按需分配,动态升级。

秘密武器:对象头(Object Header)

在 Java 中,每个对象的内存结构里都有一个叫 Mark Word(标记字) 的区域(在 64 位虚拟机上占 8 个字节 / 64 位)。

这 8 个字节是并发的核心。它并不直接存储一个复杂的锁对象,而是像一个“多功能瑞士军刀”,根据锁的状态,复用这 64 位的存储空间:

锁状态 Mark Word 存储的内容 解释
无锁状态 (001) 对象的 HashCode、分代年龄等 绝大多数 Java 对象的一生都是这个状态。 根本没有锁,完全不浪费额外内存。
偏向锁 (101) 持有锁的线程 ID、分代年龄 JVM 发现只有“你”这一个线程老来拿锁,干脆直接把你的名字写在对象头上。几乎没有性能和内存开销。
轻量级锁 (00) 指向当前线程栈中锁记录的指针 出现轻微竞争时,利用 CAS(自旋)把指针指向线程自己的栈,依然不需要向操作系统申请重锁。
重量级锁 (10) 指向 Monitor 对象(管程)的指针 只有当激烈竞争发生时,JVM 才会真正创建一个重量级的 Monitor 锁对象,并把指针存到这里。

💡 总结来说: > 只要你不用 synchronized,或者没有多线程激烈的抢夺,这个对象头里存的就只是 HashCode 和垃圾回收数据。锁的属性只是这 64 位数据在特定状态下的“临时兼职”,并没有为锁单独开辟额外的内存空间。


3. 现代 Java 的反思:这种设计完美吗?

虽然 JVM 优化到了极致,但“万物皆可为锁”的设计在今天看来,确实带有时代的局限性。

  1. 语义模糊: 一个用来存数据的 User 对象,同时还能用来当锁,这违反了“单一职责原则”。
  2. 现代 Java 的转向: 自 Java 5 引入 java.util.concurrent(如 ReentrantLock)以来,官方更推荐显式锁。而在现代高并发开发中,大家已经很少直接在普通业务对象上用 synchronized 了。