目录

一、前言

二、synchronized 六大核心特性

1. 原子性(核心特性)

2. 可见性

3. 有序性

4. 可重入性

5. 自动释放锁

6. 非公平锁(默认)

三、synchronized 三种使用方式

1. 修饰普通实例方法

2. 修饰静态方法

3. 修饰代码块(推荐使用)

① 锁 this 当前实例

② 锁自定义对象(项目最常用)

③ 锁 Class 类对象

四、synchronized 锁机制原理(重点)

1. 锁的本质:锁住的是对象

2. JDK 1.6 锁升级机制

(1)什么是锁升级?

(2)三级锁逐个讲解

1. 偏向锁(没人抢、只有一个线程用)

2. 轻量级锁(少量线程抢、竞争不激烈)

3. 重量级锁(很多线程疯狂竞争)

(3)、关键总结(背这几句就够)

3. 等待唤醒机制

4. 底层实现核心

五、使用注意事项

六、博客总结


一、前言

synchronized 是 Java 内置的悲观互斥锁关键字,核心作用是解决多线程并发下共享变量的线程安全问题,保证多线程操作的原子性、可见性和有序性。

与 ReentrantLock 相比,它无需手动加锁、解锁,由 JVM 底层自动管理,语法简单、安全性高,是日常并发编程中最基础、最常用的同步方案。

二、synchronized 六大核心特性

synchronized 的核心特性决定了它的同步能力,逐一拆解如下:

1. 原子性(核心特性)

 被 synchronized 修饰的代码块/方法,同一时刻只能被一个线程执行,操作不可拆分、不可中断。

举例:多线程同时修改一个共享变量,同步后不会出现数据争抢、脏数据(如多线程累加同一个变量,最终结果正确)。

2. 可见性

当一个线程修改了 synchronized 保护的共享变量后,会立刻将修改后的值刷新到主内存,其他线程再次读取该变量时,能直接获取最新值,解决了多线程内存不可见问题。

3. 有序性

禁止 JVM 对同步范围内的代码进行指令重排序,保证代码按编写顺序执行,避免因重排序导致的并发逻辑错乱。

4. 可重入性

同一个线程在获取锁之后,再次进入由同一把锁保护的同步代码/方法时,无需重新争抢锁,可直接获取,不会出现自己阻塞自己的死锁情况。

举例:同步方法 A 内部调用同步方法 B(两把锁为同一对象),线程进入 A 后,可直接进入 B,无需再次加锁。

5. 自动释放锁

无需手动解锁,JVM 自动管理锁的释放,两种场景下会自动释放:

  • 同步代码/方法正常执行结束,自动释放锁;

  • 同步代码/方法抛出异常,无论异常类型,都会自动释放锁,避免死锁。

6. 非公平锁(默认)

synchronized 默认是非公平锁,新来的线程可以直接抢占锁,不保证线程排队顺序,这样的设计能提升并发吞吐效率。


三、synchronized 三种使用方式

synchronized 有三种使用场景,核心区别在于锁对象不同,粒度不同,适用场景也不同,优先推荐同步代码块(粒度更细、性能更好)。

1. 修饰普通实例方法

锁对象:当前实例对象(this)

特点:同一实例对象的多个线程互斥,不同实例对象之间互不影响,不互斥。

public class SyncDemo {
    // 修饰普通实例方法
    // 锁对象:SyncDemo 的实例对象(this)
    public synchronized void testSyncMethod() {
        // 同步业务逻辑(如修改共享变量)
        System.out.println("普通实例方法同步");
    }
}

2. 修饰静态方法

锁对象:当前类的 Class 对象(全局唯一)

特点:锁住整个类,所有该类的实例对象共用同一把锁,全局互斥。

public class SyncDemo {
    // 修饰静态同步方法
    // 锁对象:SyncDemo.class 类对象
    public static synchronized void testSyncStaticMethod() {
        System.out.println("静态方法全局同步");
    }
}
  • 普通同步方法:锁对象 this,同对象互斥,不同对象不互斥。
  • 静态同步方法:锁 Class 类对象,全局唯一,所有对象都互斥。

3. 修饰代码块(推荐使用)

锁对象:可手动指定(this、自定义对象、Class 对象),灵活度最高。

特点:只锁住关键同步代码,不锁整个方法,锁粒度更细,并发效率更高。

① 锁 this 当前实例
public void testSyncBlockThis() {
    synchronized (this) {
        System.out.println("同步代码块 - 锁 this");
    }
}
② 锁自定义对象(项目最常用)
public class SyncDemo {
    // 单独定义锁对象
    private final Object lock = new Object();

    public void testSyncBlockObj() {
        // 锁自定义对象
        synchronized (lock) {
            System.out.println("同步代码块 - 锁自定义对象");
        }
    }
}
③ 锁 Class 类对象
public void testSyncBlockClass() {
    synchronized (SyncDemo.class) {
        System.out.println("同步代码块 - 锁 Class 对象");
    }
}

四、synchronized 锁机制原理(重点)

synchronized 的底层由 JVM 实现,核心依赖 Monitor 监视器和对象头,JDK 1.6 对其进行了重大优化,引入锁升级机制,大幅提升性能。

1. 锁的本质:锁住的是对象

synchronized 锁的不是代码,而是对象。每一个 Java 对象底层都包含一个 对象头(Mark Word),锁的标记、锁状态、持有线程等信息,都存储在对象头中。

简单理解:给代码加 synchronized,本质是给指定的对象“上锁”,线程要执行同步代码,必须先获取该对象的锁。

2. JDK 1.6 锁升级机制

为了优化 synchronized 的性能,JVM 引入了“锁升级”机制,锁会根据竞争激烈程度,从低开销向高开销逐级升级,只能升级,不能降级。

public class SyncDemo {
    // 隐含:锁 = 当前实例对象 this
    public synchronized void testSyncMethod() {
        System.out.println("普通实例方法同步");
    }
}

先记住 3 个前提

  1. new SyncDemo() 出来的每一个对象,头上都有一块「小标签」叫 Mark Word(对象头)
  2. synchronized 不加锁时、加轻锁、加重锁,状态都记在这个对象头里
  3. 锁升级只能往上升级,不能降级

(1)什么是锁升级?

JDK1.6 之前:只要用 synchronized 一上来就是重量级锁,开销特别大,很慢。

JDK1.6 之后做了优化:根据竞争线程多少,自动一步步升级:偏向锁 → 轻量级锁 → 重量级锁

就像坐车:没人抢 → 独享专车(偏向锁)少数人抢 → 轮流排队不停车(轻量级锁自旋)很多人疯抢 → 只能进车站候车室等(重量级锁阻塞)


(2)三级锁逐个讲解

1. 偏向锁(没人抢、只有一个线程用)

场景:整个程序就一个线程反复调用你的 testSyncMethod(),没有其他线程来抢。

特点:

  • JVM 默认偏向第一个进来的线程
  • 直接放行,几乎没有加锁开销
  • 就像这个房间专门给你一个人专用,不用每次开锁关门

👉 适用:单线程操作同步方法,无竞争。


2. 轻量级锁(少量线程抢、竞争不激烈)

场景:来了两三个线程轮流抢同一个对象的 testSyncMethod()

特点:

  • 不把线程挂起、不进操作系统阻塞
  • 线程在原地循环重试抢锁(叫:自旋)
  • 谁抢到谁执行,抢不到就一直循环试

比喻:两个人抢一间办公室,没抢到不坐下等,就在门口来回踱步、不停看什么时候空出来,不睡觉阻塞

👉 适用:线程少、抢锁时间很短。


3. 重量级锁(很多线程疯狂竞争)

场景:一堆线程同时抢同一个对象的同步方法,自旋抢不到,一直空转浪费 CPU。

这时直接升级为重量级锁

  • 抢不到锁的线程直接阻塞,进入等待队列
  • 让出 CPU,不瞎循环了
  • 依靠操作系统内核调度唤醒

比喻:人太多了,门口踱步没用,直接去候车室坐着等,有空了再叫你。

缺点:开销最大、最慢;优点:不浪费 CPU。

(3)、关键总结(背这几句就够)

  1. 锁升级顺序:偏向锁 → 轻量级锁 → 重量级锁,只能升不能降。
  2. 偏向锁:单线程无竞争,开销最小。
  3. 轻量级锁:少量竞争,线程自旋不阻塞。
  4. 重量级锁:高并发竞争,线程阻塞等待,开销最大。
  5. 所有锁状态,都存在Java 对象的对象头 Mark Word 里,你那个 SyncDemo 实例对象自带这个标记。

3. 等待唤醒机制

synchronized 可配合 Object 类的wait()notify()notifyAll() 方法,实现线程的等待与唤醒,常用于生产者-消费者模型。

  • wait():线程释放持有的锁,进入等待池阻塞,等待被唤醒;

  • notify():随机唤醒一个处于等待池中的线程,唤醒后线程需重新争抢锁;

  • notifyAll():唤醒所有处于等待池中的线程,所有线程重新争抢锁。

注意:wait()、notify()、notifyAll() 必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。

4. 底层实现核心

  • synchronized 底层基于 Monitor 监视器实现,Monitor 是 JVM 内部的一个对象,用于管理锁的竞争和线程等待;

  • 重量级锁依赖操作系统的 互斥量(Mutex) 实现,线程阻塞和唤醒会切换内核态与用户态,开销较大;

  • 锁的状态(偏向锁、轻量级锁、重量级锁),均存储在对象头的 Mark Word 中,JVM 通过修改 Mark Word 的值,实现锁的升级和切换。


五、使用注意事项

  • 优先使用 同步代码块,缩小锁粒度,只锁住关键代码,避免锁住整个方法,提升并发性能;

  • 不要用 String、Integer 等常量、包装类作为锁对象,这类对象可能被缓存,导致多个线程共用同一把锁,引发意外互斥;

  • 线程等待时,建议用while 循环判断等待条件,而非 if,防止线程被虚假唤醒后,跳过条件判断直接执行代码;

  • 简单并发场景(无特殊需求)优先用 synchronized,语法简单、不易出错;复杂场景(需要公平锁、超时等待、多条件唤醒)再考虑 ReentrantLock。


六、博客总结

synchronized 作为 Java 内置的同步机制,核心优势是简单、安全、无需手动管理锁的释放,其核心特性(原子性、可见性、有序性、可重入性)保证了多线程并发安全。

三种使用方式对应不同的锁对象,同步代码块因粒度细、性能优,是日常开发的首选;底层基于对象头和 Monitor 实现,JDK 1.6 的锁升级机制(偏向锁→轻量级锁→重量级锁)大幅优化了其性能。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐