synchronized
概述
synchronized 是 Java 语言中的关键字,是一种内置锁,用于实现线程的同步。它的主要目的是确保多个线程在同一时刻,只能有一个线程可以进入被 synchronized 修饰的代码段或方法
一、用法
synchronized 主要有三种使用方式。
1. 同步实例方法
锁是当前实例对象。
public class Counter {private int count = 0;// 同步实例方法,锁是 this,即当前Counter对象实例public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}
当一个线程调用 counter1.increment() 时,它会获取 counter1 这个对象实例的锁。其他线程要调用 counter1 的任何一个同步实例方法(如 getCount())都会被阻塞。但是,如果另一个线程操作的是 不同的实例(如 counter2),则不会受影响,因为它们获取的是不同对象的锁。
2. 同步静态方法
锁是当前类的 Class 对象。
public class StaticCounter {private static int count = 0;// 同步静态方法,锁是 StaticCounter.classpublic static synchronized void increment() {count++;}
}
因为静态方法属于类而不属于任何实例,所以锁是类的 Class 对象。这意味着,即使有多个 StaticCounter 的实例,所有线程在执行任何一个同步静态方法时,都需要竞争同一把锁。
3. 同步代码块
可以更细粒度地控制锁的范围和锁的对象,提升并发效率。
- 
指定对象作为锁:
public class FineGrainedCounter {private int count = 0;private final Object lock = new Object(); // 专门用于锁的对象public void increment() {// 同步代码块,锁是 lock 对象synchronized (lock) {count++;}// 其他不需要同步的代码可以放在这里,减少锁的持有时间} }使用一个专门的私有锁对象是一个好习惯,可以防止外部代码意外获取到你的锁,导致死锁或其他并发问题。
 - 
使用
this:public void method() {// 同步代码块,锁是当前实例对象,等同于同步实例方法的一部分synchronized (this) {// ...} } - 
使用
.class:public void staticMethod() {// 同步代码块,锁是类的Class对象,等同于同步静态方法的一部分synchronized (FineGrainedCounter.class) {// ...} } 
二、原理
synchronized 的实现原理涉及到 Java 对象头 和 Monitor(监视器)。
1. Java 对象头
在 JVM 中,每个对象在内存中分为三部分:
- 对象头
 - 实例数据
 - 对齐填充
 
其中,对象头 是 synchronized 实现锁的关键。它主要包含两部分:
- Mark Word:存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、持有锁的线程 ID 等。
 - Klass Pointer:对象指向它的类元数据的指针。
 
2. Monitor(监视器锁)
每一个 Java 对象都与一个 Monitor 相关联。可以把 Monitor 理解为一个特殊的、线程私有的数据结构。当线程执行到 synchronized 保护的代码时,其执行流程如下:
- 进入同步代码块:线程尝试通过对象的对象头获取与之关联的 Monitor。
 - 获取锁:
- 如果 Monitor 的计数器为 0(表示未被任何线程持有),当前线程会成功获取该 Monitor,并将计数器设置为 1。此时,该线程是 Monitor 的持有者。
 - 如果当前线程已经持有该 Monitor,它可以重入,计数器会再次加 1(这就是 
synchronized是可重入锁的原因)。 - 如果 Monitor 被其他线程持有,当前线程会被阻塞,进入 同步队列,状态变为 
BLOCKED。 
 - 执行代码:获取锁成功后,线程执行同步代码块内的代码。
 - 退出同步代码块:当线程执行完同步代码块,或者抛出异常时,它会释放 Monitor,将计数器减 1。
- 如果计数器减为 0,表示线程完全释放了锁。
 - 然后,它会唤醒在同步队列中等待的线程,让它们重新竞争锁。
 
 
这个“竞争”过程是由 JVM 和操作系统底层协作完成的,是非公平的。
3. 锁的升级过程

JVM 通过读取并解析锁对象的 Mark Word 中的锁标志位和偏向锁标志位,来精确判断锁的当前状态,从而决定是直接获取锁、升级锁、还是进入自旋/阻塞。
为了在性能和开销之间取得平衡,Java 6 之后,synchronized 的锁状态是可以升级的,而不是一成不变的重量级锁。这个过程是单向的。
锁的级别从低到高依次为:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
- 
偏向锁:
- 场景:假设在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。
 - 原理:当一个线程访问同步块时,会在对象头和栈帧中的锁记录里存储偏向的线程 ID。以后该线程再进入和退出同步块时,不需要进行 CAS 操作来加锁和解锁,只需简单测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁。
 - 目的:消除在无竞争情况下的同步开销。
 
 - 
轻量级锁:
- 场景:当偏向锁被另一个线程访问时(发生竞争),偏向锁会升级为轻量级锁。
 - 原理:线程在执行同步块之前,JVM 会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中。然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,当前线程会通过自旋来尝试获取锁。
 - 目的:在响应速度和高吞吐量之间取得平衡。
 
 - 
重量级锁:
- 场景:如果轻量级锁自旋失败(比如自旋次数超过阈值,或者有多个线程在竞争),锁就会膨胀为重量级锁。
 - 原理:此时,Mark Word 中存储的是指向重量级锁(Monitor)的指针。等待锁的线程都会进入阻塞状态。(有了操作系统参与,当线程无法获取重量级锁时:线程调用 pthread_mutex_lock() 等系统调用,陷入内核态(通过软中断/系统调用门),内核检查锁状态:如果锁可用:立即获取,返回用户态,如果锁被占用:将线程放入等待队列,标记为阻塞状态
调度器选择其他就绪线程运行) - 特点:开销最大,但能避免 CPU 空转。
 
 
