Java synchronized关键字详解
Java synchronized 详解
一、synchronized 基本概念
1.1 什么是 synchronized
synchronized 是 Java 中的关键字,用于实现线程同步,确保多个线程在访问共享资源时的线程安全。它是 Java 内置的、最基本的互斥同步机制。
1.2 主要作用
- 互斥性:保证同一时刻只有一个线程可以执行被 synchronized 修饰的代码
- 可见性:确保线程对共享变量的修改对其他线程立即可见
- 有序性:禁止指令重排序,保证代码执行顺序
二、synchronized 的三种使用方式
2.1 同步实例方法
public class Counter {
private int count = 0;
// 同步实例方法,锁是当前对象实例(this)
public synchronized void increment() {
count++;
}
// 等价于以下代码:
public void increment2() {
synchronized(this) {
count++;
}
}
// 获取当前值(也需要同步以保证可见性)
public synchronized int getCount() {
return count;
}
}
特点:
- 锁对象是当前实例(this)
- 不同实例之间的同步互不影响
- 适用于保护实例变量的线程安全
2.2 同步静态方法
public class StaticCounter {
private static int count = 0;
// 同步静态方法,锁是当前类的 Class 对象
public static synchronized void increment() {
count++;
}
// 等价于以下代码:
public static void increment2() {
synchronized(StaticCounter.class) {
count++;
}
}
// 错误的写法:不同锁对象无法保护静态变量
public void wrongIncrement() {
synchronized(this) { // 锁是实例,无法保护静态变量
count++; // 线程不安全!
}
}
}
特点:
- 锁对象是类的 Class 对象(如 StaticCounter.class)
- 所有实例共享同一把锁
- 适用于保护静态变量的线程安全
2.3 同步代码块
public class Example {
// 使用专门的锁对象(推荐)
private final Object lock = new Object();
private int value = 0;
private int anotherValue = 0;
public void updateValue() {
// 同步代码块,锁是 lock 对象
synchronized(lock) {
value++;
}
}
public void updateTwoValues() {
// 保护多个相关变量
synchronized(lock) {
value++;
anotherValue--;
}
}
// 使用不同锁对象保护不同资源
private final Object accountLock = new Object();
private double balance = 0;
public void deposit(double amount) {
synchronized(accountLock) {
balance += amount;
}
}
}
特点:
- 灵活性高,可以指定任意对象作为锁
- 可以精确控制同步范围
- 使用专门的锁对象(而非 this)可以避免外部干扰
三、synchronized 的特性
3.1 可重入性(Reentrancy)
public class ReentrantExample {
public synchronized void methodA() {
System.out.println("methodA");
methodB(); // 可重入:同一个线程可以再次获取同一把锁
}
public synchronized void methodB() {
System.out.println("methodB");
}
// 可重入的同步代码块
public void reentrantBlock() {
Object lock = new Object();
synchronized(lock) {
System.out.println("First lock");
synchronized(lock) { // 同一个线程,可以再次获取同一把锁
System.out.println("Second lock");
}
}
}
}
可重入性意义:
- 防止线程自己把自己锁死
- 支持递归调用
- 支持在同步方法中调用另一个同步方法
3.2 不可中断性
public class InterruptExample {
private final Object lock = new Object();
public void testInterrupt() {
Thread t1 = new Thread(() -> {
synchronized(lock) {
try {
System.out.println("Thread 1 got lock, sleeping...");
Thread.sleep(5000); // 持有锁期间可以响应中断
} catch (InterruptedException e) {
System.out.println("Thread 1 interrupted while holding lock");
}
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000); // 确保 t1 先获取锁
System.out.println("Thread 2 trying to get lock...");
synchronized(lock) { // 等待锁时不可中断
System.out.println("Thread 2 got lock");
}
} catch (InterruptedException e) {
System.out.println("Thread 2 interrupted while waiting for lock");
}
});
t1.start();
t2.start();
try {
Thread.sleep(2000);
t2.interrupt(); // 尝试中断正在等待锁的 t2
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:
- 等待锁时不可中断,会一直阻塞直到获取锁
- 获取锁后执行期间可以响应中断
- 考虑使用
Lock接口的lockInterruptibly()方法如果需要可中断的锁
3.3 非公平性
public class FairnessExample {
private final Object lock = new Object();
public void accessResource() {
synchronized(lock) {
// 新来的线程可能比等待队列中的线程先获取锁
// 这种非公平策略可以减少线程切换开销,提高吞吐量
System.out.println(Thread.currentThread().getName() + " got lock");
}
}
public void testFairness() throws InterruptedException {
// 启动多个线程竞争锁
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
accessResource();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread-" + i).start();
}
}
}
公平性影响:
- 优点:非公平锁吞吐量更高(减少线程切换)
- 缺点:可能导致线程饥饿(某些线程长时间获取不到锁)
- 适用场景:大部分情况下非公平锁性能更好
四、对象的内存布局与锁标识
4.1 对象的内存结构
在 HotSpot 虚拟机中,对象在内存中的布局分为三部分:
|-------------------------------------------------------------------|
| Object in Memory |
|-------------------------------------------------------------------|
| Object Header | Instance Data | Padding (optional) |
|-------------------------------------------------------------------|
-
对象头(Object Header)
- Mark Word:存储对象运行时数据(哈希码、GC分代年龄、锁状态等),这部分数据的长度在32位和64位的虚拟机种分别为32bit和64bit,官方称它为“Mark Word”
- Klass Pointer:指向类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。32位4字节,64位开启指针压缩或最大堆内存<32g时4字节,否则8字节。JDK8默认开启指针压缩后位4字节,再JVM参数中关闭指针压缩(-XX:UseCompressedOop)后,长度为8字节。
- 数组长度(仅数组对象有):如果对象是一个数组,那在对象头中还必须有一块数据用于记录数组长度。4字节

-
实例数据(Instance Data)
- 对象真正存储的有效数据(各字段内容)
-
对齐填充(Padding)
- 确保对象大小为8字节的整数倍

4.2 Mark Word 结构详解
32位 JVM 的 Mark Word 结构:
|-------------------------------------------------------|
| Mark Word (32 bits) |
|-------------------------------------------------------|
| lock | biased_lock | age | identity_hashcode |
|-------------------------------------------------------|
| 2bit | 1bit | 4bit | 25bit |
|-------------------------------------------------------|
64位 JVM 的 Mark Word 结构:
|---------------------------------------------------------------|
| Mark Word (64 bits) |
|---------------------------------------------------------------|
| unused | lock | biased_lock | age | identity_hashcode |
|---------------------------------------------------------------|
| 25bit | 2bit | 1bit | 4bit | 31bit |
|---------------------------------------------------------------|
锁状态与 Mark Word 对应关系:
32位JVM下的对象结构描述

64位JVM下的对象结构描述

- ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争时,JVM使用原子操作而不是OS互斥,这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的MarkWord中设置指向锁记录的指针。
- ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同是在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针
更直观的理解方式:
| 锁状态 | 锁标识位 (lock) | 偏向锁标识 (biased_lock) | 存储内容 |
|---|---|---|---|
| 无锁 | 01 | 0 | 哈希码、分代年龄 |
| 偏向锁 | 01 | 1 | 线程ID、时间戳、分代年龄 |
| 轻量级锁 | 00 | - | 指向栈中锁记录的指针 |
| 重量级锁 | 10 | - | 指向监视器(monitor)的指针 |
| GC标记 | 11 | - | 标记为垃圾回收相关状态 |
4.3 使用 JOL 工具查看对象布局
JOL(Java Object Layout)是 OpenJDK 提供的工具,用于分析对象内存布局。
添加依赖:
<dependency>
<groupId>org.openjdk.jolgroupId>
<artifactId>jol-coreartifactId>
<version>0.16version>
dependency>
示例代码:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class ObjectLayoutDemo {
public static void main(String[] args) {
// 查看虚拟机详细信息
System.out.println(VM.current().details());
// 查看简单对象布局
Object obj = new Object();
System.out.println("====== Object layout ======");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// 查看数组对象布局
int[] array = new int[3];
System.out.println("====== Array layout ======");
System.out.println(ClassLayout.parseInstance(array).toPrintable());
// 查看自定义对象布局
MyClass myObj = new MyClass();
System.out.println("====== MyClass layout ======");
System.out.println(ClassLayout.parseInstance(myObj).toPrintable());
}
static class MyClass {
private int id;
private String name;
private boolean flag;
}
}

- OFFSET:偏移地址,单位为字节;
- SIZE:占用的内存大小,单位为字节;
- TYPE DESCRIPTION:类型描述,其中object header为对象头;
- VALUE:对应内存中当前存储的值,二进制32为;
这里需要注意:使用的是操作系统的小端存储,高位在左边,低位在右边。如果我们要看,就要反过来看(00000001 00000000 00000000 00000000)==> 00000000 00000000 00000000 00000001
关闭指针压缩后,对象头为16字节:-XX:UseCompressedOops

五、锁升级过程详解
5.1 完整锁升级流程图
┌─────────────┐
│ 无锁状态 │
│ (001) │
└──────┬──────┘
│ 线程首次获取锁
▼
┌─────────────┐
│ 偏向锁 │ ◄─┐
│ (101) │ │ 同一线程再次获取
└──────┬──────┘ │
│ 其他线程 │
│ 竞争锁 │
▼ │
┌─────────────┐ │
│ 轻量级锁 │ │
│ (000) │ │
└──────┬──────┘ │
│ 自旋失败 │ │
│ 或竞争激烈│ │
▼ │
┌─────────────┐ │
│ 重量级锁 │ │
│ (010) │ │
└─────────────┘ │
▲ │
│ 释放锁 │
└──────────┘
5.2 无锁状态
public class NoLockState {
public static void main(String[] args) {
Object obj = new Object();
// 刚创建的对象处于无锁状态,未出现任何获取锁的时候
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
输出分析:
- 锁标识位:01(无锁/偏向锁)
- 偏向锁标识:0(无锁状态)
- 此时对象头主要存储哈希码(调用 hashCode() 后生成)和分代年龄

JDK 17的变化
- 偏向锁默认禁用:JDK 15开始,偏向锁默认被禁用,所以新建对象显示为
non-biasable - 对象头初始状态直接为无锁(001),而不是以前的
101(可偏向)
5.3 偏向锁
偏向锁是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。
public class BiasedLockDemo {
public static void main(String[] args) throws InterruptedException {
// JVM 默认延迟 4 秒开启偏向锁,休眠确保偏向锁已启用
Thread.sleep(5000);
Object obj = new Object();
System.out.println("====== 初始状态(匿名偏向)======");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("====== 获取偏向锁后 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println("====== 释放偏向锁后 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
偏向锁特点:
- 适用场景:只有一个线程访问同步块
- 优点:加锁解锁无额外消耗(仅 CAS 设置线程ID)
- 匿名偏向:对象创建后、首次获取锁前的状态(偏向锁开启但未偏向任何线程)
- 偏向锁延迟:JVM 启动时有很多同步操作,默认延迟 4 秒开启偏向锁
JVM 参数:
-XX:BiasedLockingStartupDelay=0 # 关闭偏向锁延迟
-XX:+UseBiasedLocking # 启用偏向锁(JDK 15 后默认禁用)
-XX:-UseBiasedLocking # 禁用偏向锁


最开始还没偏向某个线程的时候,状态是偏向锁,但是偏向锁的线程还没有,这种称为匿名偏向
JDK 17的变化

在JDK17中,偏向锁已经被废弃,默认是禁用的。所以,即使代码中尝试获取偏向锁,实际上也会使用轻量级锁。因此,这里的“获取偏向锁后”实际上是指获取轻量级锁后。
5.4 轻量级锁
倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段此时MarkWord的结构也变为轻量级锁的结构。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。
偏向锁到轻量级锁是偏向锁获取失败就直接升级,没有自旋操作。
public class LightweightLockDemo {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000); // 等待偏向锁启用
Object lock = new Object();
// 线程1获取偏向锁
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("t1 持有偏向锁");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
});
t1.start();
t1.join();
// 线程2竞争锁,触发偏向锁撤销,升级为轻量级锁
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 触发轻量级锁");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
});
t2.start();
t2.join();
System.out.println("最终状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
}
轻量级锁特点:
- 适用场景:多线程交替执行同步块,无实际竞争
- 实现方式:在栈帧中创建锁记录(Lock Record),通过 CAS 将对象头指向锁记录
- 自旋优化:竞争失败时会自旋尝试获取锁,避免立即升级重量级锁
- 升级条件:CAS 操作失败或自旋超过阈值

JDK 17的变化

由于偏向锁被禁用,t1获取锁后,对象头存储的是指向t1线程栈中Lock Record的指针,这里标记为“thin lock”,表示这是一个轻量级锁。在轻量级锁中,对象头中的标记字(mark word)被替换为指向线程栈中锁记录(Lock Record)的指针。这个指针是0x000000fc907ff188。
当t2尝试获取同一个锁时,发生了锁竞争。此时,锁可能会升级为重量级锁,但这里仍然显示为“thin lock”,并且指针值发生了变化(从0x000000fc907ff188变为0x000000fc907ff458)。这表明锁已经转移到了另一个线程(t2)的锁记录,但仍然是轻量级锁。实际上,在轻量级锁竞争时,会通过自旋尝试获取锁,如果自旋失败,则会升级为重量级锁。但这里显示为轻量级锁,可能表示t2在自旋过程中成功获取了锁(或者是在轻量级锁竞争时,通过CAS操作将锁记录指针替换为t2的锁记录指针)。
5.5 重量级锁
在升级到重量级锁之前,先创建一个monitor对象,进行自旋,自旋失败再阻塞【重量级锁里面的park和unpark】
public class HeavyweightLockDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
// 创建多个线程激烈竞争同一把锁
Runnable task = () -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获取锁");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动多个线程同时竞争
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(task, "Thread-" + i);
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
// 查看最终锁状态
System.out.println("锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
}
重量级锁特点:
- 适用场景:多线程激烈竞争同步资源
- 实现方式:通过操作系统的互斥量(mutex)实现
- 性能影响:涉及用户态到内核态的切换,开销较大
- 包含机制:等待队列、条件变量等完整同步机制

JDK 17的变化

- fat lock:明确表示对象处于重量级锁状态
- 指针值:
0x000001f7de2a44f2指向堆中的Monitor对象 - 状态码:最后两位是
10(虽然显示为16进制,但从f2的二进制看)
六、Monitor(管程/监视器)机制
6.1 什么是 Monitor
Monitor(管程)是一种并发编程的构造,用于管理对共享资源的访问。它包含:
- 共享变量
- 操作共享变量的过程/方法
- 初始化代码
- 同步机制(互斥访问)
6.2 Java 中的 Monitor 实现
java.lang.Object类定义了 wait(),notify(),notifyAll()方法,这些方法的具体实现,依赖于ObjectMonitor实现,这是JVM内部基于C++实现的一套机制。
在 JVM 中,每个对象都与一个 Monitor 关联(ObjectMonitor):
// hotspot/src/share/vm/runtime/objectMonitor.hpp
class ObjectMonitor {
// 指向持有锁的线程(标识拥有该monitor的线程)
void * volatile _owner;
// 等待锁的线程队列(Contention List)(多线程竞争锁会会先存到这个单向链表中(FILO栈结构))
ObjectWaiter * volatile _cxq;
// 入口队列(Entry List)(存放在进入或重新进入时被阻塞(b1ocked)的线程(也是存竞争锁失败的线程))
ObjectWaiter * volatile _EntryList;
// 等待队列(Wait Set) - 调用 wait() 的线程(等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点)
ObjectWaiter * volatile _WaitSet;
// 锁的重入次数
volatile int _recursions;
// 其他字段...
};
6.3 Monitor 工作流程
┌─────────────────────────────────────────────┐
│ ObjectMonitor │
│ │
│ Owner: Thread-X (持有锁的线程) │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Entry List / CXQ │ │
│ │ (等待锁的线程队列,非公平竞争) │ │
│ │ Thread-A → Thread-B → Thread-C │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Wait Set │ │
│ │ (调用 wait() 的线程队列) │ │
│ │ Thread-Y → Thread-Z │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘
6.4 synchronized 与 Monitor 的关系
public class MonitorExample {
private final Object lock = new Object();
private int sharedData = 0;
public void synchronizedMethod() {
synchronized(lock) {
// 进入同步块时:
// 1. 尝试获取 lock 关联的 Monitor
// 2. 如果获取成功,设置 _owner 为当前线程
// 3. 如果获取失败,进入 _cxq 队列等待
sharedData++;
try {
// 调用 wait() 时:
// 1. 释放锁,_owner 置为 null
// 2. 线程进入 _WaitSet 队列
// 3. 等待被 notify() 唤醒
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被唤醒后:
// 1. 从 _WaitSet 移到 _EntryList
// 2. 重新竞争锁
}
}
public void notifyMethod() {
synchronized(lock) {
// 调用 notify() 时:
// 1. 从 _WaitSet 移出一个线程到 _EntryList
// 2. 该线程将重新参与锁竞争
lock.notify();
}
}
}
6.5 MESA 管程模型
Java 的 synchronized 基于 MESA 管程模型实现:
┌─────────────────────────────────────────┐
│ MESA 模型 │
├─────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ │
│ │ 共享变量 │ │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ 入口等待队列 │ │
│ │ (Entry Queue) │ │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ 条件变量等待队列 │ │
│ │ (Condition Queue) │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────┘

七、synchronized 底层实现原理
7.1 字节码层面分析
同步方法:
public class BytecodeExample {
private int count = 0;
public synchronized void increment() {
count++;
}
}
编译后查看字节码:
javac BytecodeExample.java
javap -c -v BytecodeExample
同步方法字节码:
public synchronized void increment();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED # 方法访问标志包含 ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
同步代码块字节码:
public void incrementBlock() {
synchronized(this) {
count++;
}
}
public void incrementBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter # 进入同步块
4: aload_0
5: dup
6: getfield #2 // Field count:I
9: iconst_1
10: iadd
11: putfield #2 // Field count:I
14: aload_1
15: monitorexit # 正常退出同步块
16: goto 24
19: astore_2
20: aload_1
21: monitorexit # 异常退出同步块
22: aload_2
23: athrow
24: return
7.2 monitorenter 和 monitorexit
JVM 规范中对这两个指令的描述:
-
monitorenter:
- 每个对象都与一个 monitor 关联
- 如果 monitor 的进入计数器为 0,线程成为 monitor 的 owner
- 如果线程已经拥有该 monitor,只需增加进入计数器(可重入)
- 如果其他线程拥有该 monitor,线程阻塞直到 monitor 的进入计数器为 0
-
monitorexit:
- 将 monitor 的进入计数器减 1
- 如果计数器变为 0,线程释放 monitor
7.3 ACC_SYNCHRONIZED 标志
对于同步方法:
- 方法访问标志中包含
ACC_SYNCHRONIZED - JVM 在调用方法时自动获取锁
- 方法执行完成时自动释放锁
- 异常退出时也会自动释放锁
八、synchronized 优化技术
8.1 自适应自旋锁
原理:
- JVM 根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定自旋时间
- 如果自旋刚刚成功过,则允许自旋更长时间
- 如果很少自旋成功,则可能直接省略自旋过程
优势:
- 避免固定自旋次数的缺点
- 智能适应实际运行情况
- 减少不必要的 CPU 消耗
8.2 锁消除
public class LockEliminationDemo {
/**
* 锁消除测试
* JVM 参数:
* -XX:+EliminateLocks 开启锁消除(默认开启)
* -XX:-EliminateLocks 关闭锁消除
*/
public String concatString(String s1, String s2, String s3) {
// StringBuffer 内部方法是 synchronized 的
// 但这里的 sb 是局部变量,不可能被其他线程访问
// JVM 会进行锁消除优化
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
public static void main(String[] args) {
LockEliminationDemo demo = new LockEliminationDemo();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
demo.concatString("Hello", " ", "World");
}
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start) + "ms");
}
}
锁消除条件:
- 对象是局部变量(不会逃逸出方法)
- 不可能被其他线程访问
- 开启逃逸分析(-XX:+DoEscapeAnalysis,默认开启)
8.3 锁粗化
public class LockCoarseningDemo {
private final Object lock = new Object();
// 锁粗化前:多次加锁解锁
public void beforeCoarsening() {
for (int i = 0; i < 100; i++) {
synchronized(lock) { // 每次循环都加锁解锁
// 少量操作
System.out.print(i);
}
}
}
// 锁粗化后:一次加锁解锁
public void afterCoarsening() {
synchronized(lock) { // JVM 自动优化为循环外加锁
for (int i = 0; i < 100; i++) {
System.out.print(i);
}
}
}
// JVM 会自动优化的例子
public void autoCoarsening() {
for (int i = 0; i < 100; i++) {
synchronized(lock) {
// 连续的对同一锁的加锁解锁操作
// JVM 会检测并自动进行锁粗化
}
}
}
}
锁粗化原则:
- 将连续的多个加锁解锁操作合并为一个
- 减少锁操作的开销
- 适度扩大锁范围,避免过度同步
8.4 逃逸分析
public class EscapeAnalysisDemo {
private static class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
// 方法逃逸:对象作为返回值
public Point createPoint1(int x, int y) {
return new Point(x, y); // 方法逃逸
}
// 线程逃逸:对象赋值给类变量
private static Point globalPoint;
public void createPoint2(int x, int y) {
globalPoint = new Point(x, y); // 线程逃逸
}
// 无逃逸:对象未逃逸出方法
public int calculateDistance(int x1, int y1, int x2, int y2) {
Point p1 = new Point(x1, y1); // 无逃逸
Point p2 = new Point(x2, y2); // 无逃逸
// JVM 可能进行栈上分配,避免堆内存分配
int dx = p1.x - p2.x;
int dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
}
逃逸分析优化:
- 栈上分配:无逃逸对象在栈上分配,自动销毁
- 标量替换:将对象拆分为基本类型,在栈上分配
- 同步消除:无逃逸对象的同步操作可消除
九、synchronized 与 volatile 的比较
9.1 详细对比
| 特性 | synchronized | volatile | 说明 |
|---|---|---|---|
| 互斥性 | ✅ 保证 | ❌ 不保证 | synchronized 是排他锁 |
| 可见性 | ✅ 保证 | ✅ 保证 | 两者都遵循 happens-before 原则 |
| 原子性 | ✅ 保证 | ❌ 有限保证 | volatile 只保证单次读/写的原子性 |
| 有序性 | ✅ 保证 | ✅ 保证 | 防止指令重排序 |
| 使用成本 | 较高 | 较低 | synchronized 涉及锁操作 |
| 适用场景 | 复杂同步逻辑 | 状态标志、双重检查锁 | |
| 可重入性 | ✅ 支持 | ❌ 不支持 | |
| 锁升级 | ✅ 支持 | ❌ 不支持 | 偏向锁→轻量级锁→重量级锁 |
9.2 正确使用 volatile
public class VolatileCorrectUsage {
// 正确用法1:状态标志
private volatile boolean running = true;
public void stop() {
running = false;
}
public void work() {
while (running) {
// 执行任务
}
}
// 正确用法2:一次性安全发布
private volatile Resource resource;
public Resource getResource() {
if (resource == null) {
synchronized(this) {
if (resource == null) {
resource = new Resource(); // volatile 保证可见性
}
}
}
return resource;
}
// 错误用法:volatile 不能保证复合操作的原子性
private volatile int count = 0;
public void wrongIncrement() {
count++; // 这不是原子操作!
// 实际是:read → modify → write 三步
}
public synchronized void correctIncrement() {
count++; // 使用 synchronized 保证原子性
}
}
十、常见使用场景与最佳实践
10.1 单例模式(双重检查锁)
public class Singleton {
// volatile 禁止指令重排序,保证可见性
private volatile static Singleton instance;
private Singleton() {
// 防止反射创建实例
if (instance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance.");
}
}
public static Singleton getInstance() {
// 第一次检查:避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
// 第二次检查:确保只有一个实例被创建
if (instance == null) {
instance = new Singleton();
/*
* 对象的创建过程:
* 1. 分配内存空间
* 2. 初始化对象
* 3. 将引用指向内存地址
*
* 如果没有 volatile,可能发生指令重排序:
* 1. 分配内存空间
* 3. 将引用指向内存地址(此时对象还未初始化)
* 2. 初始化对象
*
* 这会导致其他线程拿到未初始化的对象!
*/
}
}
}
return instance;
}
// 防止反序列化破坏单例
protected Object readResolve() {
return getInstance();
}
}
10.2 线程安全的计数器
public class ThreadSafeCounter {
// 方案1:使用 synchronized
private int count1 = 0;
public synchronized void increment1() {
count1++;
}
public synchronized int getCount1() {
return count1;
}
// 方案2:使用 AtomicInteger(更高效)
private AtomicInteger count2 = new AtomicInteger(0);
public void increment2() {
count2.incrementAndGet();
}
public int getCount2() {
return count2.get();
}
// 方案3:使用 LongAdder(高并发场景最优)
private LongAdder count3 = new LongAdder();
public void increment3() {
count3.increment();
}
public long getCount3() {
return count3.sum();
}
}
10.3 生产者-消费者模式
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public ProducerConsumer(int capacity) {
this.capacity = capacity;
}
public void produce(int value) throws InterruptedException {
synchronized(queue) {
// 使用 while 而不是 if,防止虚假唤醒
while (queue.size() == capacity) {
System.out.println("队列已满,生产者等待...");
queue.wait();
}
queue.offer(value);
System.out.println("生产: " + value + ", 队列大小: " + queue.size());
// 通知所有等待的消费者
queue.notifyAll();
}
}
public int consume() throws InterruptedException {
synchronized(queue) {
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
queue.wait();
}
int value = queue.poll();
System.out.println("消费: " + value + ", 队列大小: " + queue.size());
// 通知所有等待的生产者
queue.notifyAll();
return value;
}
}
// 测试代码
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer(5);
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
pc.produce(i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
pc.consume();
Thread.sleep(150);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
10.4 读写锁模式
public class ReadWriteCounter {
private int readCount = 0;
private int writeCount = 0;
// 分离读锁和写锁
private final Object readLock = new Object();
private final Object writeLock = new Object();
public void incrementRead() {
synchronized(readLock) {
readCount++;
}
}
public void incrementWrite() {
synchronized(writeLock) {
writeCount++;
}
}
// 更复杂的读写控制
public class BetterReadWriteLock {
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
private final Object lock = new Object();
public void readLock() throws InterruptedException {
synchronized(lock) {
while (writers > 0 || writeRequests > 0) {
lock.wait();
}
readers++;
}
}
public void readUnlock() {
synchronized(lock) {
readers--;
if (readers == 0) {
lock.notifyAll();
}
}
}
public void writeLock() throws InterruptedException {
synchronized(lock) {
writeRequests++;
while (readers > 0 || writers > 0) {
lock.wait();
}
writeRequests--;
writers++;
}
}
public void writeUnlock() {
synchronized(lock) {
writers--;
lock.notifyAll();
}
}
}
}
十一、性能优化与最佳实践
11.1 锁粒度优化
public class LockGranularity {
// 粗粒度锁:性能差
public class CoarseGrainedLock {
private final Object lock = new Object();
private Map<String, String> users = new HashMap<>();
private Map<String, String> products = new HashMap<>();
public void updateUser(String id, String info) {
synchronized(lock) { // 锁住所有资源
users.put(id, info);
}
}
public void updateProduct(String id, String info) {
synchronized(lock) { // 不必要的竞争
products.put(id, info);
}
}
}
// 细粒度锁:性能好
public class FineGrainedLock {
private final Object userLock = new Object();
private final Object productLock = new Object();
private Map<String, String> users = new HashMap<>();
private Map<String, String> products = new HashMap<>();
public void updateUser(String id, String info) {
synchronized(userLock) { // 只锁用户数据
users.put(id, info);
}
}
public void updateProduct(String id, String info) {
synchronized(productLock) { // 只锁产品数据
products.put(id, info);
}
}
}
// 分段锁:进一步提高并发度
public class SegmentLock {
private static final int SEGMENT_COUNT = 16;
private final Object[] locks = new Object[SEGMENT_COUNT];
private Map<String, String>[] maps = new HashMap[SEGMENT_COUNT];
public SegmentLock() {
for (int i = 0; i < SEGMENT_COUNT; i++) {
locks[i] = new Object();
maps[i] = new HashMap<>();
}
}
public void put(String key, String value) {
int segment = Math.abs(key.hashCode()) % SEGMENT_COUNT;
synchronized(locks[segment]) {
maps[segment].put(key, value);
}
}
public String get(String key) {
int segment = Math.abs(key.hashCode()) % SEGMENT_COUNT;
synchronized(locks[segment]) {
return maps[segment].get(key);
}
}
}
}
11.2 避免常见陷阱
public class SynchronizationPitfalls {
// 陷阱1:锁字符串(字符串池导致意外共享)
public void stringLockPitfall(String userId) {
// 危险!字符串可能来自常量池,不同参数可能指向同一对象
synchronized(userId) {
// 业务逻辑
}
}
// 解决方案:使用专用锁对象
private final Map<String, Object> userLocks = new ConcurrentHashMap<>();
public void safeStringLock(String userId) {
// 每个用户有独立的锁对象
Object lock = userLocks.computeIfAbsent(userId, k -> new Object());
synchronized(lock) {
// 业务逻辑
}
}
// 陷阱2:锁方法内的局部对象
public void localLockPitfall() {
Object lock = new Object(); // 每次调用都创建新对象
synchronized(lock) { // 毫无作用!
// 每个线程都有自己的锁对象,不会互斥
}
}
// 陷阱3:忘记在循环内检查条件
public class ConditionCheckPitfall {
private Queue<String> queue = new LinkedList<>();
public String get() throws InterruptedException {
synchronized(queue) {
// 错误:应该用 while 而不是 if
if (queue.isEmpty()) {
queue.wait();
}
// 被唤醒后可能队列仍然是空的(虚假唤醒)
return queue.poll();
}
}
// 正确做法
public String getCorrect() throws InterruptedException {
synchronized(queue) {
while (queue.isEmpty()) { // 使用 while 循环检查
queue.wait();
}
return queue.poll();
}
}
}
// 陷阱4:锁顺序不一致导致死锁
public class DeadlockRisk {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void method1() {
synchronized(lockA) { // 先获取 lockA
synchronized(lockB) { // 再获取 lockB
// 操作共享资源
}
}
}
public void method2() {
synchronized(lockB) { // 先获取 lockB - 顺序不一致!
synchronized(lockA) { // 再获取 lockA
// 可能发生死锁
}
}
}
// 解决方案:固定锁顺序
public void safeMethod1() {
synchronized(lockA) {
synchronized(lockB) {
// 操作
}
}
}
public void safeMethod2() {
synchronized(lockA) { // 和 method1 相同的顺序
synchronized(lockB) {
// 操作
}
}
}
}
}
11.3 性能监控与分析
public class LockPerformanceMonitor {
// 监控锁竞争情况
public static void monitorLockContention() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 获取线程信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
for (ThreadInfo threadInfo : threadInfos) {
// 检查线程状态和锁信息
if (threadInfo.getLockName() != null) {
System.out.println("线程: " + threadInfo.getThreadName());
System.out.println(" 等待锁: " + threadInfo.getLockName());
System.out.println(" 锁拥有者: " + threadInfo.getLockOwnerName());
System.out.println(" 阻塞次数: " + threadInfo.getBlockedCount());
System.out.println(" 阻塞时间: " + threadInfo.getBlockedTime() + "ms");
}
}
}
// 检测死锁
public static void detectDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 查找死锁
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("发现死锁!涉及线程:");
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(" - " + threadInfo.getThreadName());
System.out.println(" 等待锁: " + threadInfo.getLockName());
System.out.println(" 锁拥有者: " + threadInfo.getLockOwnerName());
}
} else {
System.out.println("未检测到死锁");
}
}
// 记录锁等待时间
public static class TimedLock {
private final Object lock = new Object();
private long totalWaitTime = 0;
private long lockCount = 0;
public void doWork() throws InterruptedException {
long startWait = System.nanoTime();
synchronized(lock) {
long waitTime = System.nanoTime() - startWait;
totalWaitTime += waitTime;
lockCount++;
// 执行业务逻辑
Thread.sleep(10);
}
// 定期报告性能指标
if (lockCount % 1000 == 0) {
System.out.printf("平均锁等待时间: %.3f ms%n",
(totalWaitTime / 1_000_000.0) / lockCount);
}
}
}
}
十二、与其它同步机制比较
12.1 synchronized vs Lock(ReentrantLock)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class SynchronizedVsLock {
// synchronized 实现
public class SynchronizedExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized(lock) {
count++;
}
}
public void decrement() {
synchronized(lock) {
count--;
}
}
}
// ReentrantLock 实现
public class LockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 确保锁被释放
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
// Lock 的高级特性
public void advancedFeatures() {
ReentrantLock advancedLock = new ReentrantLock(true); // 公平锁
// 1. 尝试获取锁(可设置超时)
boolean acquired = advancedLock.tryLock();
if (acquired) {
try {
// 执行业务逻辑
} finally {
advancedLock.unlock();
}
}
// 2. 可中断的锁获取
try {
advancedLock.lockInterruptibly(); // 可响应中断
try {
// 执行业务逻辑
} finally {
advancedLock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. 多个条件变量
Condition condition = advancedLock.newCondition();
advancedLock.lock();
try {
while (!someCondition()) {
condition.await(); // 释放锁并等待
}
// 条件满足,继续执行
condition.signalAll(); // 唤醒等待的线程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
advancedLock.unlock();
}
}
private boolean someCondition() {
return false;
}
}
}
12.2 选择建议
| 场景 | 推荐使用 | 理由 |
|---|---|---|
| 简单的同步需求 | synchronized | 代码简洁,自动管理锁 |
| 需要公平锁 | ReentrantLock | synchronized 是非公平的 |
| 需要尝试获取锁 | ReentrantLock | tryLock() 方法 |
| 需要可中断的锁 | ReentrantLock | lockInterruptibly() |
| 需要多个条件变量 | ReentrantLock | 多个 Condition 对象 |
| 读多写少场景 | ReentrantReadWriteLock | 读写分离提高并发 |
| 超高并发计数 | LongAdder | 分段计数减少竞争 |
| 简单的状态标志 | volatile | 轻量级,保证可见性 |
十三、现代 JVM 的优化趋势
13.1 JDK 版本对 synchronized 的改进
| JDK 版本 | 主要改进 | 影响 |
|---|---|---|
| JDK 1.6 | 引入锁升级机制(偏向锁、轻量级锁) | 大幅提升无竞争/低竞争场景性能 |
| JDK 1.7 | 移除偏向锁延迟,优化自旋策略 | 进一步优化锁性能 |
| JDK 1.8 | 默认开启偏向锁,优化锁消除 | 继续微调锁性能 |
| JDK 15 | 默认禁用偏向锁 | 因维护成本高且实际收益有限 |
| JDK 后续 | 继续优化锁实现,减少开销 | 保持与其它同步机制的竞争力 |
13.2 替代方案考虑
public class ModernAlternatives {
// 1. 使用 java.util.concurrent 工具类
public void useConcurrentTools() {
// 并发集合
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
// 原子类
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
// 并发计数器
LongAdder adder = new LongAdder();
adder.increment();
// 屏障
CountDownLatch latch = new CountDownLatch(3);
CyclicBarrier barrier = new CyclicBarrier(3);
}
// 2. 使用 StampedLock(乐观读)
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
private double x, y;
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock(); // 获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 乐观读
double currentX = x, currentY = y;
if (!lock.validate(stamp)) { // 检查是否被修改
stamp = lock.readLock(); // 升级为悲观读锁
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
// 3. 考虑无锁编程(CAS)
public class CASExample {
private volatile int value;
public void increment() {
int oldValue;
int newValue;
do {
oldValue = value;
newValue = oldValue + 1;
} while (!compareAndSet(oldValue, newValue));
}
private boolean compareAndSet(int expect, int update) {
// 模拟 CAS 操作
// 实际使用 Unsafe 或原子类
return true;
}
}
}
十四、常见面试问题
14.1 基础问题
-
synchronized 的作用是什么?
- 保证原子性、可见性、有序性
- 实现线程间的互斥同步
-
synchronized 的三种用法?
- 同步实例方法(锁是当前实例)
- 同步静态方法(锁是当前类的 Class 对象)
- 同步代码块(锁是指定对象)
-
synchronized 和 volatile 的区别?
- synchronized 保证原子性,volatile 不保证
- synchronized 阻塞,volatile 不阻塞
- synchronized 可重入,volatile 不可重入
14.2 原理深入
-
synchronized 的底层实现原理?
- 同步代码块:monitorenter 和 monitorexit 指令
- 同步方法:ACC_SYNCHRONIZED 标志
- 依赖对象头的 Mark Word 和 Monitor 机制
-
什么是锁升级?过程是怎样的?
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 根据竞争情况动态调整锁策略
-
偏向锁、轻量级锁、重量级锁的区别?
- 偏向锁:只有一个线程访问
- 轻量级锁:多线程交替访问,无竞争
- 重量级锁:多线程竞争激烈
14.3 实践问题
-
双重检查锁中为什么要加 volatile?
- 防止指令重排序
- 保证可见性
- 避免返回未完全初始化的对象
-
synchronized 是否是公平锁?
- 不是,synchronized 是非公平锁
- 新线程可能比等待队列中的线程先获取锁
-
如何优化 synchronized 的性能?
- 减小锁粒度
- 减少锁持有时间
- 使用读写分离
- 考虑无锁数据结构
十五、总结
synchronized 作为 Java 内置的同步机制,经历了多年的发展和优化:
- 简单易用:语法简洁,自动管理锁的获取和释放
- 功能全面:保证原子性、可见性、有序性三大特性
- 持续优化:锁升级机制大幅提升了性能
- JVM 支持:与 JVM 深度集成,享受各种运行时优化
使用建议:
- 对于简单的同步需求,优先考虑 synchronized
- 关注锁粒度,避免过度同步
- 在高并发场景中,评估替代方案(如并发工具类)
- 利用 JVM 参数调优锁行为(如偏向锁设置)
未来趋势:
- 随着并发编程模型的发展,synchronized 仍是基础但重要的工具
- 新的同步机制(如 VarHandle、Memory Ordering)提供更多选择
- 无锁编程、Actor 模型等为高并发场景提供新思路
掌握 synchronized 不仅是为了使用它,更是为了理解 Java 并发编程的基础原理。无论技术如何发展,这些基本原理都是构建高效、可靠并发系统的基石。








