跳到主要内容

Java synchronized完全指南 🔒

本文适合:零基础初学者、准备面试的工程师、需要系统性掌握synchronized的开发者

🚀 目录

1. 什么是synchronized?为什么需要它?

🤔 先看一个问题

class UnsafeCounter {
private int count = 0;

public void increment() {
count++; // 看似简单,但实际不是原子操作!
}

public int getCount() {
return count;
}
}

// 多线程环境下使用
UnsafeCounter counter = new UnsafeCounter();

// 创建10个线程,每个线程增加1000次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
}).start();
}

// 理想结果:10000
// 实际结果:通常小于10000,且每次运行结果不同
System.out.println("Final count: " + counter.getCount());

问题:为什么结果不是10000?

原因分析

  1. 非原子性count++ 实际包含三个步骤:
    • 读取count的值
    • 将值加1
    • 将新值写回count
  2. 线程竞争:多个线程同时执行这些步骤会产生数据混乱
  3. 内存可见性:一个线程的修改可能不会立即被其他线程看到

💡 synchronized的作用

synchronized 是Java提供的同步机制,它解决了并发编程的三大问题:

class SafeCounter {
private int count = 0;

// 使用synchronized保证线程安全
public synchronized void increment() {
count++; // 现在是原子操作了!
}

public synchronized int getCount() {
return count;
}
}

// 同样的多线程测试,结果始终是10000!
SafeCounter counter = new SafeCounter();
// ... 相同的测试代码
// 结果:Final count: 10000 ✅

🎯 synchronized的三大作用

  1. 原子性:确保代码块内的操作不可分割,要么全部执行,要么都不执行
  2. 可见性:确保一个线程的修改对其他线程可见
  3. 有序性:防止指令重排序,保证代码按预期顺序执行

📊 问题对比

特性不使用synchronized使用synchronized
线程安全❌ 不安全✅ 安全
结果一致性❌ 每次不同✅ 结果一致
数据完整性❌ 可能损坏✅ 保证完整
性能✅ 稍快(但不正确)⚡ 合理的开销

2. synchronized的基本使用

2.1 三种使用方式

class SynchronizedUsage {
private final Object lock = new Object(); // 私有锁对象
private static int staticCount = 0;

// 方式1:修饰实例方法 - 对象锁
public synchronized void instanceMethod() {
System.out.println("实例方法同步");
// 锁定的是当前对象实例:this
// 等价于 synchronized(this)
}

// 方式2:修饰静态方法 - 类锁
public static synchronized void staticMethod() {
System.out.println("静态方法同步");
// 锁定的是类对象:SynchronizedUsage.class
// 等价于 synchronized(SynchronizedUsage.class)
}

// 方式3:修饰代码块 - 灵活锁定
public void blockMethod() {
// 同步代码块,锁定当前对象
synchronized(this) {
System.out.println("同步代码块 - 锁定this");
}

// 同步代码块,锁定类对象
synchronized(SynchronizedUsage.class) {
System.out.println("同步代码块 - 锁定类对象");
}

// 同步代码块,锁定指定对象(推荐)
synchronized(lock) {
System.out.println("同步代码块 - 锁定私有对象");
}
}
}

2.2 完整示例:银行转账

class BankAccount {
private double balance;
private final String accountNumber;

public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}

// 存款 - 使用synchronized保证线程安全
public synchronized void deposit(double amount) {
if (amount > 0) {
System.out.println(Thread.currentThread().getName() +
" 存款: " + amount);
balance += amount;
System.out.println("当前余额: " + balance);
}
}

// 取款 - 使用synchronized保证线程安全
public synchronized void withdraw(double amount) {
if (amount > 0 && balance >= amount) {
System.out.println(Thread.currentThread().getName() +
" 取款: " + amount);
balance -= amount;
System.out.println("当前余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" 取款失败: 余额不足");
}
}

// 查询余额 - 使用synchronized保证读取到最新值
public synchronized double getBalance() {
return balance;
}

// 转账 - 需要锁定两个账户(后面会讲如何避免死锁)
public void transfer(BankAccount target, double amount) {
if (amount <= 0 || this.balance < amount) {
System.out.println("转账失败:金额无效或余额不足");
return;
}

// 简化版本:只锁定当前账户
synchronized(this) {
this.balance -= amount;
System.out.println(this.accountNumber + " 转出: " + amount);
}

synchronized(target) {
target.balance += amount;
System.out.println(target.accountNumber + " 转入: " + amount);
}
}
}

// 测试银行账户的线程安全性
public class BankTest {
public static void main(String[] args) throws InterruptedException {
BankAccount account = new BankAccount("ACC001", 1000);

// 创建多个线程进行存款和取款
Thread[] threads = new Thread[5];

for (int i = 0; i < threads.length; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 100; j++) {
if (threadId % 2 == 0) {
account.deposit(10); // 偶数线程存款
} else {
account.withdraw(5); // 奇数线程取款
}
try {
Thread.sleep(1); // 模拟处理时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Thread-" + threadId);
}

// 启动所有线程
for (Thread thread : threads) {
thread.start();
}

// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}

System.out.println("最终余额: " + account.getBalance());
// 理论计算:1000 + (3个存款线程) * 100 * 10 - (2个取款线程) * 100 * 5
// = 1000 + 3000 - 1000 = 3000
}
}

3. 对象锁 vs 类锁

3.1 锁的类型对比

class LockTypes {
// 对象锁实例
public synchronized void objectLockMethod() {
System.out.println("对象锁方法 - " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

// 类锁静态方法
public static synchronized void classLockMethod() {
System.out.println("类锁方法 - " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

// 对象锁代码块
public void objectLockBlock() {
synchronized(this) {
System.out.println("对象锁代码块 - " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

// 类锁代码块
public void classLockBlock() {
synchronized(LockTypes.class) {
System.out.println("类锁代码块 - " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

3.2 锁的互斥关系演示

class LockMutexDemo {
private final LockTypes lockTypes = new LockTypes();

public void demonstrateObjectLock() {
System.out.println("=== 对象锁演示 ===");

// 多个线程访问同一个对象的对象锁方法 - 串行执行
Thread t1 = new Thread(() -> lockTypes.objectLockMethod(), "T1");
Thread t2 = new Thread(() -> lockTypes.objectLockMethod(), "T2");
Thread t3 = new Thread(() -> lockTypes.objectLockBlock(), "T3");

t1.start();
t2.start();
t3.start();

// 等待完成
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

public void demonstrateClassLock() {
System.out.println("=== 类锁演示 ===");

// 多个线程访问类锁方法 - 串行执行
Thread t1 = new Thread(() -> LockTypes.classLockMethod(), "T1");
Thread t2 = new Thread(() -> LockTypes.classLockMethod(), "T2");
Thread t3 = new Thread(() -> lockTypes.classLockBlock(), "T3");

t1.start();
t2.start();
t3.start();

// 等待完成
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

public void demonstrateDifferentLocks() {
System.out.println("=== 不同锁类型演示 ===");

// 对象锁和类锁不互斥 - 可以并行执行
Thread t1 = new Thread(() -> lockTypes.objectLockMethod(), "对象锁-T1");
Thread t2 = new Thread(() -> LockTypes.classLockMethod(), "类锁-T2");

t1.start();
t2.start();

// 等待完成
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

public static void main(String[] args) throws InterruptedException {
LockMutexDemo demo = new LockMutexDemo();

demo.demonstrateObjectLock();
Thread.sleep(1000);
demo.demonstrateClassLock();
Thread.sleep(1000);
demo.demonstrateDifferentLocks();
}
}

3.3 锁类型总结

锁类型锁定对象互斥范围常用场景
实例方法synchronized当前对象实例 this同一个实例的不同synchronized方法实例级别的资源保护
静态方法synchronized类对象 ClassName.class所有实例的synchronized静态方法全局资源保护
synchronized(this)当前对象实例 this同一个实例与实例方法相同
synchronized(ClassName.class)类对象 ClassName.class所有实例与静态方法相同
synchronized(私有对象)指定的私有对象锁定同一个对象的所有代码块推荐的方式,粒度可控

3.4 最佳实践

class BestPractices {
// ✅ 推荐:使用私有对象作为锁
private final Object dataLock = new Object();
private final Object configLock = new Object();
private int data = 0;
private String config = "";

public void updateData(int newData) {
synchronized(dataLock) {
this.data = newData;
}
}

public void updateConfig(String newConfig) {
synchronized(configLock) {
this.config = newConfig;
}
}

// ❌ 不推荐:直接使用this作为锁
public void badPractice() {
synchronized(this) { // 外部代码也可能获取this锁
// 业务逻辑
}
}

// ❌ 不推荐:使用字符串字面量
public void anotherBadPractice() {
synchronized("lock") { // 可能与其他使用相同字符串的代码产生竞争
// 业务逻辑
}
}

// ❌ 不推荐:使用可变对象作为锁
private Integer lockObject = 0;
public void mutableLockProblem() {
synchronized(lockObject) {
lockObject++; // 锁对象变了!会导致锁失效
}
}
}

4. synchronized的底层原理

4.1 Monitor机制(管程)

每个Java对象都可以作为锁,锁的底层实现是Monitor(管程):

// 简化的Monitor结构(概念理解)
class Monitor {
private Object owner; // 持有锁的线程
private int recursions; // 重入次数
private EntryList entryList; // 等待获取锁的线程队列
private WaitSet waitSet; // 调用wait()的线程集合

// 线程尝试获取锁时的逻辑
void enter(Thread thread) {
if (owner == null || owner == thread) {
// 无竞争或重入
owner = thread;
recursions++;
} else {
// 有竞争,加入等待队列
entryList.add(thread);
blockThread(thread); // 阻塞线程
}
}

// 线程释放锁时的逻辑
void exit(Thread thread) {
if (owner == thread) {
recursions--;
if (recursions == 0) {
owner = null;
// 唤醒等待队列中的一个线程
Thread nextThread = entryList.remove();
if (nextThread != null) {
unblockThread(nextThread);
}
}
}
}
}

4.2 对象头结构

Java对象的头部包含了锁的信息,在64位JVM中:

对象头 (Object Header) = Mark Word + 类型指针 + 数组长度(如果是数组)

Mark Word (8字节) 在不同锁状态下的结构:

┌─────────────────────────────────────────────────────┐
│ 无锁状态 │ unused:25 │ hash:31 │ unused:1 │ age:4 │ 0 │ 01 │
├─────────────────────────────────────────────────────┤
│ 偏向锁 │ thread:54 │ epoch:2 │ age:4 │ 1 │ 01 │
├─────────────────────────────────────────────────────┤
│ 轻量级锁 │ 指向Lock Record的指针 │ 00 │
├─────────────────────────────────────────────────────┤
│ 重量级锁 │ 指向Monitor的指针 │ 10 │
├─────────────────────────────────────────────────────┤
│ GC标记 │ 转发地址 │ 11 │
└─────────────────────────────────────────────────────┘

4.3 synchronized的执行流程

class SynchronizedExecution {
public void executeWithSynchronized() {
// 1. JVM检查对象头的锁状态
// 2. 根据锁状态执行不同的获取逻辑
synchronized(this) {
// 3. 成功获取锁后执行同步代码
System.out.println("执行同步代码");
// 4. 代码执行完毕,准备释放锁
}
// 5. 自动释放锁(JVM保证)
}
}

4.4 重入锁机制

synchronized支持重入,即同一个线程可以多次获取同一个锁:

class ReentrantExample {
public synchronized void method1() {
System.out.println("method1开始");
method2(); // 重入获取锁
System.out.println("method1结束");
}

public synchronized void method2() {
System.out.println("method2开始");
method3(); // 再次重入
System.out.println("method2结束");
}

public synchronized void method3() {
System.out.println("method3开始");
System.out.println("method3结束");
}

public static void main(String[] args) {
ReentrantExample example = new ReentrantExample();

// 单线程测试重入
example.method1();
/* 输出:
method1开始
method2开始
method3开始
method3结束
method2结束
method1结束
*/
}
}

重入计数器工作原理:

  1. 第一次获取锁:计数器 = 1,owner = 当前线程
  2. 第二次获取锁:计数器 = 2
  3. 第三次获取锁:计数器 = 3
  4. 释放锁:计数器 = 2
  5. 释放锁:计数器 = 1
  6. 释放锁:计数器 = 0,真正释放锁,唤醒其他线程

5. 锁的升级过程详解

5.1 锁的四种状态

5.2 详细升级过程

class LockEscalationDemo {
private final Object lock = new Object();

// 阶段1:偏向锁(单线程场景)
public void biasedLockScenario() {
// 第一次获取锁:对象从无锁升级为偏向锁
synchronized(lock) {
System.out.println("第一次获取偏向锁");
// JVM在对象头中记录当前线程ID
}
// 释放锁:不做任何操作,仍然偏向当前线程

// 后续获取:直接检查偏向线程ID即可
for (int i = 0; i < 5; i++) {
synchronized(lock) {
System.out.println("再次获取偏向锁,无开销");
}
}
}

// 阶段2:轻量级锁(线程交替执行场景)
public void lightweightLockScenario() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
synchronized(lock) {
System.out.println("线程1:轻量级锁执行中");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "Thread-1");

Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
synchronized(lock) {
System.out.println("线程2:轻量级锁执行中");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "Thread-2");

t1.start();
t2.start();
t1.join();
t2.join();
}

// 阶段3:重量级锁(多线程竞争场景)
public void heavyweightLockScenario() throws InterruptedException {
Thread[] threads = new Thread[10];

for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 5; j++) {
synchronized(lock) {
System.out.println(Thread.currentThread().getName() +
":重量级锁执行中");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "HeavyThread-" + i);
}

// 启动所有线程,模拟激烈竞争
for (Thread thread : threads) {
thread.start();
}

// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
}

public static void main(String[] args) throws InterruptedException {
LockEscalationDemo demo = new LockEscalationDemo();

System.out.println("=== 偏向锁演示 ===");
demo.biasedLockScenario();

Thread.sleep(1000);
System.out.println("\n=== 轻量级锁演示 ===");
demo.lightweightLockScenario();

Thread.sleep(1000);
System.out.println("\n=== 重量级锁演示 ===");
demo.heavyweightLockScenario();
}
}

5.3 锁升级的条件和特点

锁状态升级条件实现方式线程状态性能开销适用场景
无锁初始状态无同步运行最低无并发需求
偏向锁单线程首次获取记录线程ID运行很低单线程长期使用
轻量级锁其他线程竞争CAS + 锁记录自旋等待中等线程交替执行
重量级锁CAS失败多次操作系统互斥量阻塞等待最高多线程竞争激烈

5.4 锁升级的性能测试

class LockPerformanceTest {
private static final int ITERATIONS = 1000000;

// 测试无锁性能
public static void testNoLock() {
long start = System.nanoTime();
int counter = 0;
for (int i = 0; i < ITERATIONS; i++) {
counter++;
}
long end = System.nanoTime();
System.out.printf("无锁性能: %d ms%n", (end - start) / 1_000_000);
}

// 测试偏向锁性能(单线程)
public static void testBiasedLock() {
Object lock = new Object();
long start = System.nanoTime();

for (int i = 0; i < ITERATIONS; i++) {
synchronized(lock) {
// 偏向锁:单线程重复获取
}
}

long end = System.nanoTime();
System.out.printf("偏向锁性能: %d ms%n", (end - start) / 1_000_000);
}

// 测试轻量级锁性能(双线程交替)
public static void testLightweightLock() throws InterruptedException {
Object lock = new Object();
AtomicInteger counter = new AtomicInteger(0);

long start = System.nanoTime();

Thread t1 = new Thread(() -> {
for (int i = 0; i < ITERATIONS / 2; i++) {
synchronized(lock) {
counter.incrementAndGet();
}
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < ITERATIONS / 2; i++) {
synchronized(lock) {
counter.incrementAndGet();
}
}
});

t1.start();
t2.start();
t1.join();
t2.join();

long end = System.nanoTime();
System.out.printf("轻量级锁性能: %d ms (结果: %d)%n",
(end - start) / 1_000_000, counter.get());
}

public static void main(String[] args) throws InterruptedException {
System.out.println("=== 锁性能对比测试 ===");
System.out.println("测试次数: " + ITERATIONS);

testNoLock();
testBiasedLock();
testLightweightLock();

System.out.println("注意:实际性能取决于JVM参数和硬件环境");
}
}

6. synchronized的内存语义

6.1 happens-before关系

happens-before是Java内存模型中的重要概念,定义了操作之间的可见性保证:

class HappensBeforeExample {
private int data = 0;
private boolean ready = false;
private final Object lock = new Object();

// 写入线程
public void writer() {
synchronized(lock) {
data = 42; // 1. 写入数据
ready = true; // 2. 设置标志
} // 3. 释放锁 - happens-before后续获取锁
}

// 读取线程
public void reader() {
synchronized(lock) {
// 4. 获取锁 - 保证能看到释放锁前的所有写操作
if (ready) { // 5. 读取ready,能看到data=42
System.out.println("Data: " + data);
}
} // 6. 释放锁
}
}

内存语义保证

  • 解锁操作 happens-before 后续对同一个锁的加锁操作
  • 线程A解锁时,所有对共享变量的修改都会刷新到主内存
  • 线程B加锁时,会从主内存读取最新的共享变量值

6.2 内存屏障机制

JVM通过插入内存屏障来保证synchronized的内存语义:

class MemoryBarrierExample {
private volatile int sharedData = 0;
private final Object lock = new Object();

public void updateData() {
// 在synchronized块开始处插入Load屏障和Acquire屏障
synchronized(lock) {
// Acquire屏障:禁止后面的读操作重排序到前面
sharedData = 100; // 写操作
// Store屏障:禁止前面的写操作重排序到后面
}
// 在synchronized块结束处插入Release屏障
}

public int readData() {
synchronized(lock) {
// Acquire屏障:禁止后面的读操作重排序到前面
return sharedData; // 读操作
}
}
}

6.3 可见性保证演示

class VisibilityDemo {
private boolean flag = false;
private int data = 0;
private final Object lock = new Object();

public static void main(String[] args) throws InterruptedException {
VisibilityDemo demo = new VisibilityDemo();

// 写入线程
Thread writer = new Thread(() -> {
synchronized(demo.lock) {
demo.flag = true;
demo.data = 42;
System.out.println("写入线程: flag=" + demo.flag + ", data=" + demo.data);
}
}, "Writer");

// 读取线程
Thread reader = new Thread(() -> {
try {
Thread.sleep(100); // 确保写入线程先执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

synchronized(demo.lock) {
// 由于synchronized的内存语义,一定能看到flag=true和data=42
if (demo.flag) {
System.out.println("读取线程: flag=" + demo.flag + ", data=" + demo.data);
} else {
System.out.println("读取线程: flag=" + demo.flag + ", data=" + demo.data);
}
}
}, "Reader");

reader.start();
writer.start();

writer.join();
reader.join();
}
}

7. 锁优化技术

7.1 锁消除优化

JIT编译器在编译时会分析代码,消除不必要的锁:

class LockElimination {
// 场景1:局部变量的同步锁会被消除
public void localVariableSync() {
// StringBuffer是线程安全的,但这里是局部变量
StringBuffer sb = new StringBuffer(); // 局部变量,不存在线程安全问题
sb.append("Hello "); // synchronized方法
sb.append("World"); // synchronized方法
sb.append("!");
// JIT编译器会消除这些synchronized调用
}

// 场景2:逃逸分析消除锁
public void escapeAnalysis() {
StringBuilder builder = new StringBuilder();
builder.append("Thread: ");
builder.append(Thread.currentThread().getName());
// 如果分析确定builder不会逃逸到其他线程,可以消除锁
}

// 场景3:锁粗化
public void lockCoarsening() {
StringBuilder sb = new StringBuilder();
// 原始代码:细粒度锁
for (int i = 0; i < 100; i++) {
synchronized(sb) { // 每次循环都获取锁
sb.append(i);
}
}

// JIT优化后:粗粒度锁
synchronized(sb) {
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}
}
}

7.2 自适应自旋

JDK 7引入了自适应自旋,根据历史数据调整自旋次数:

class AdaptiveSpinning {
private final Object lock = new Object();

public void demonstrateAdaptiveSpinning() throws InterruptedException {
// 短时间持有锁的场景
Thread shortHolder = new Thread(() -> {
synchronized(lock) {
// 持有锁很短时间,适合自旋
System.out.println("短时间锁持有者");
}
});

// 长时间持有锁的场景
Thread longHolder = new Thread(() -> {
synchronized(lock) {
// 持有锁较长时间,不适合自旋
System.out.println("长时间锁持有者");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});

// 等待线程
Thread waiter1 = new Thread(() -> {
synchronized(lock) {
System.out.println("等待者1获取锁");
}
});

Thread waiter2 = new Thread(() -> {
synchronized(lock) {
System.out.println("等待者2获取锁");
}
});

// 场景1:短时间锁,适合自旋
System.out.println("=== 场景1:短时间锁 ===");
shortHolder.start();
waiter1.start();
waiter2.start();
Thread.sleep(100);

// 场景2:长时间锁,不适合自旋
System.out.println("=== 场景2:长时间锁 ===");
longHolder.start();
waiter1 = new Thread(() -> {
synchronized(lock) {
System.out.println("等待者1获取锁(长时间)");
}
});
waiter1.start();
}
}

7.3 锁优化JVM参数

# 开启偏向锁(默认开启)
-XX:+UseBiasedLocking

# 设置偏向锁延迟时间(JVM启动后多少秒开始偏向)
-XX:BiasedLockingStartupDelay=4000

# 开启自旋锁(JDK 6+默认开启)
-XX:+UseSpinning

# 设置自旋次数(JDK 6默认10次)
-XX:PreBlockSpin=10

# 开启自适应自旋(JDK 7+默认开启)
-XX:+UseAdaptiveSpin

# 开启锁消除(默认开启)
-XX:+EliminateLocks

# 开启锁粗化(默认开启)
-XX:+AggressiveOpts

8. 面试高频考点

8.1 synchronized的实现原理?

标准答案: synchronized基于Monitor机制实现,每个Java对象都有一个关联的Monitor。当线程尝试获取锁时:

  1. 检查对象头:查看Mark Word中的锁状态
  2. 锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  3. Monitor操作
    • 成功获取:设置owner为当前线程,增加重入计数
    • 获取失败:加入EntryList等待队列,阻塞线程
    • 释放锁:减少重入计数,唤醒等待队列中的线程

8.2 对象锁和类锁的区别?

详细对比

class LockComparison {
// 对象锁 - 锁定当前实例
public synchronized void objectLock() {
System.out.println("对象锁方法");
}

// 类锁 - 锁定Class对象
public static synchronized void classLock() {
System.out.println("类锁方法");
}

// 代码块对象锁
public void objectLockBlock() {
synchronized(this) {
System.out.println("对象锁代码块");
}
}

// 代码块类锁
public void classLockBlock() {
synchronized(LockComparison.class) {
System.out.println("类锁代码块");
}
}
}
特性对象锁类锁
锁定对象实例对象 this类对象 Class.class
作用范围同一个实例所有实例
内存地址每个实例不同全局唯一
适用场景实例变量保护静态变量保护
互斥关系同实例互斥全局互斥

8.3 偏向锁、轻量级锁、重量级锁的区别?

面试要点

class LockTypesDemo {
private final Object lock = new Object();

// 偏向锁:单线程场景
public void biasedLock() {
// 只有主线程访问,锁会偏向主线程
for (int i = 0; i < 1000; i++) {
synchronized(lock) {
// 几乎没有开销,只需要检查线程ID
}
}
}

// 轻量级锁:线程交替执行
public void lightweightLock() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronized(lock) {
// CAS操作获取锁
}
try { Thread.sleep(1); } catch (Exception e) {}
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronized(lock) {
// 自旋等待获取锁
}
try { Thread.sleep(1); } catch (Exception e) {}
}
});

t1.start(); t2.start(); t1.join(); t2.join();
}

// 重量级锁:多线程竞争
public void heavyweightLock() throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
synchronized(lock) {
// 线程阻塞在操作系统层面
}
});
}
for (Thread t : threads) { t.start(); }
for (Thread t : threads) { t.join(); }
}
}

核心区别

  • 偏向锁:单线程优化,记录线程ID,后续获取无开销
  • 轻量级锁:线程交替执行,CAS + 自旋,避免阻塞
  • 重量级锁:多线程竞争,使用操作系统互斥量,线程阻塞

8.4 synchronized和ReentrantLock的区别?

对比表格

特性synchronizedReentrantLock
获取方式JVM内置关键字API调用
释放方式自动释放手动释放(必须)
可重入✅ 支持✅ 支持
可中断❌ 不支持✅ 支持lockInterruptibly()
公平锁❌ 非公平✅ 可选公平/非公平
超时机制❌ 不支持✅ 支持tryLock(timeout)
条件变量wait/notifyCondition接口
性能JDK 6+优化后很好通常稍好,在特定场景下
使用复杂度简单复杂,需要try-finally

代码示例

class LockComparison {
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();

// synchronized方式
public void synchronizedMethod() {
synchronized(syncLock) {
try {
// 业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} // 自动释放锁
}

// ReentrantLock方式
public void reentrantLockMethod() {
reentrantLock.lock(); // 手动获取锁
try {
// 业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
reentrantLock.unlock(); // 必须手动释放锁
}
}
}

8.5 什么是锁升级?升级过程是怎样的?

锁升级过程

  1. 无锁状态

    • 对象头存储hashCode和分代年龄
    • 没有线程竞争
  2. 偏向锁获取

    • 第一个线程获取锁时,JVM将锁升级为偏向锁
    • 在对象头中记录偏向线程ID
    • 后续同一线程获取锁只需要检查线程ID
  3. 偏向锁撤销

    • 其他线程尝试获取偏向锁时
    • 撤销偏向锁,升级为轻量级锁
  4. 轻量级锁

    • 在线程栈帧中创建Lock Record
    • 使用CAS尝试将对象头指向Lock Record
    • 获取成功:执行同步代码
    • 获取失败:自旋等待
  5. 重量级锁

    • CAS自旋失败一定次数后
    • 升级为重量级锁
    • 使用操作系统的互斥量
    • 线程阻塞在ObjectMonitor的等待队列中

升级特点

  • 单向升级:锁只能从低级升级到高级,不能降级
  • 自动管理:整个过程由JVM自动管理,程序员无感知
  • 性能优化:根据竞争情况自动选择最优的锁实现

8.6 synchronized的内存可见性如何保证?

内存语义保证

  1. happens-before原则

    • 解锁操作happens-before后续对同一个锁的加锁操作
    • 确保前一个线程的修改对后续线程可见
  2. 内存屏障

    • 加锁时插入Load屏障和Acquire屏障
    • 解锁时插入Store屏障和Release屏障
    • 防止指令重排序
  3. 缓存同步

    • 解锁时:将工作内存中的共享变量刷新到主内存
    • 加锁时:使工作内存中的共享变量失效,从主内存重新加载

代码演示

class MemoryVisibilityExample {
private boolean flag = false;
private int data = 0;
private final Object lock = new Object();

// 写入线程
public void writer() {
synchronized(lock) {
flag = true; // 1. 写入flag
data = 42; // 2. 写入data
} // 3. 解锁:所有写入刷新到主内存
}

// 读取线程
public void reader() {
synchronized(lock) {
// 4. 加锁:从主内存读取最新值
if (flag) { // 5. 能看到data=42
System.out.println("Data: " + data);
}
} // 6. 解锁
}
}

9. 最佳实践与陷阱

9.1 最佳实践

class SynchronizedBestPractices {
// ✅ 实践1:使用私有对象作为锁,避免外部访问
private final Object dataLock = new Object();
private final Object configLock = new Object();
private int data = 0;
private String config = "";

public void updateData(int newData) {
synchronized(dataLock) {
this.data = newData;
}
}

public void updateConfig(String newConfig) {
synchronized(configLock) {
this.config = newConfig;
}
}

// ✅ 实践2:减少锁的持有时间
public void processData() {
// 在锁外准备数据
String result = expensiveOperation();

// 只在必要时加锁
synchronized(dataLock) {
// 快速更新共享数据
this.data += result.length();
}
}

private String expensiveOperation() {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "result";
}

// ✅ 实践3:避免在同步块中调用外部方法
public void safeExternalCall() {
Object localData;
synchronized(dataLock) {
localData = this.data; // 只在同步块内访问共享数据
}

// 在同步块外调用外部方法
externalService.process(localData);
}

// 模拟外部服务
private ExternalService externalService = new ExternalService();

static class ExternalService {
public void process(Object data) {
System.out.println("Processing: " + data);
}
}
}

9.2 常见陷阱

陷阱1:使用错误的锁对象

class WrongLockObject {
// ❌ 陷阱1:使用字符串常量
public void stringConstantLock() {
synchronized("LOCK") { // 可能与其他使用"LOCK"的代码竞争
// 业务逻辑
}
}

// ❌ 陷阱2:使用可变的包装类型
private Integer lockObject = 0;
public void mutableLockObject() {
synchronized(lockObject) {
lockObject++; // lockObject变成了新的对象!锁失效了
}
}

// ❌ 陷阱3:使用this作为锁(可能被外部访问)
public void thisLock() {
synchronized(this) { // 外部代码也可以获取this锁
// 业务逻辑
}
}

// ✅ 正确做法:使用final的私有对象
private final Object correctLock = new Object();
public void correctLockUsage() {
synchronized(correctLock) { // 私有对象,外部无法访问
// 业务逻辑
}
}
}

陷阱2:死锁

class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();

// ❌ 可能导致死锁的代码
public void potentialDeadlock() {
Thread t1 = new Thread(() -> {
synchronized(lock1) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lock2) { // t1持有lock1,等待lock2
System.out.println("线程1获取了两个锁");
}
}
});

Thread t2 = new Thread(() -> {
synchronized(lock2) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lock1) { // t2持有lock2,等待lock1
System.out.println("线程2获取了两个锁");
}
}
});

t1.start(); t2.start();
}

// ✅ 避免死锁的方法1:锁顺序一致
public void avoidDeadlockWithOrder() {
Thread t1 = new Thread(() -> {
synchronized(lock1) { // 总是先获取lock1
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lock2) {
System.out.println("线程1获取了两个锁");
}
}
});

Thread t2 = new Thread(() -> {
synchronized(lock1) { // 总是先获取lock1
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lock2) {
System.out.println("线程2获取了两个锁");
}
}
});

t1.start(); t2.start();
}

// ✅ 避免死锁的方法2:使用超时机制(需要ReentrantLock)
public void avoidDeadlockWithTimeout() {
// 这里需要使用ReentrantLock来实现
// synchronized不支持超时机制
System.out.println("synchronized不支持超时,建议使用ReentrantLock");
}
}

陷阱3:同步范围过大

class LargeSynchronizedBlock {
private final Object lock = new Object();
private List<String> data = new ArrayList<>();

// ❌ 同步范围过大,影响性能
public void badSynchronization() {
synchronized(lock) {
// 不必要在锁内的操作
String input = readFromUser(); // I/O操作
String processed = expensiveComputation(input); // CPU密集操作
String formatted = formatOutput(processed); // 字符串操作

// 只有这个操作需要同步
data.add(formatted);
}
}

// ✅ 同步范围最小化
public void goodSynchronization() {
// 在锁外执行不需要同步的操作
String input = readFromUser();
String processed = expensiveComputation(input);
String formatted = formatOutput(processed);

// 只在必要时同步
synchronized(lock) {
data.add(formatted);
}
}

private String readFromUser() {
// 模拟用户输入
return "user input";
}

private String expensiveComputation(String input) {
// 模拟耗时计算
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return input.toUpperCase();
}

private String formatOutput(String processed) {
return "Result: " + processed;
}
}

10. 常见问题与解决方案

10.1 性能问题

问题:synchronized导致的性能瓶颈

class PerformanceIssues {
private final Object lock = new Object();
private int counter = 0;

// ❌ 同步块过大,导致性能问题
public void slowIncrement() {
synchronized(lock) {
counter++; // 这部分需要同步

try {
Thread.sleep(100); // 不需要在锁内的操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

// ✅ 优化:最小化同步范围
public void fastIncrement() {
// 只同步必要的操作
synchronized(lock) {
counter++;
}

// 不需要在锁内的工作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

// ✅ 进一步优化:使用原子类
private AtomicInteger atomicCounter = new AtomicInteger(0);

public void atomicIncrement() {
atomicCounter.incrementAndGet(); // 无锁操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

10.2 活跃性问题

问题:饥饿(某些线程长时间获取不到锁)

class StarvationProblem {
private final Object lock = new Object();
private int sharedResource = 0;

// ❌ 可能导致饥饿
public void unfairAccess() {
Thread highPriorityThread = new Thread(() -> {
while (true) {
synchronized(lock) {
sharedResource++;
try {
Thread.sleep(1); // 短暂睡眠
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}, "High-Priority");

Thread lowPriorityThread = new Thread(() -> {
while (true) {
synchronized(lock) {
sharedResource--;
try {
Thread.sleep(10); // 较长睡眠
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}, "Low-Priority");

highPriorityThread.start();
lowPriorityThread.start();

// 低优先级线程可能长时间获取不到锁
}

// ✅ 解决方案:使用公平锁(需要ReentrantLock)
private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁

public void fairAccess() {
Thread thread1 = new Thread(() -> {
while (true) {
fairLock.lock();
try {
sharedResource++;
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
fairLock.unlock();
}
}
}, "Thread-1");

Thread thread2 = new Thread(() -> {
while (true) {
fairLock.lock();
try {
sharedResource--;
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
fairLock.unlock();
}
}
}, "Thread-2");

thread1.start();
thread2.start();
// 使用公平锁,线程按FIFO顺序获取锁
}
}

10.3 调试和监控

监控synchronized的性能

class SynchronizedMonitoring {
private static final Map<String, AtomicLong> lockMetrics = new ConcurrentHashMap<>();
private static final Map<String, AtomicLong> waitTimes = new ConcurrentHashMap<>();

// 监控工具方法
public static void monitorLock(String lockName, Runnable task) {
long startTime = System.nanoTime();

synchronized(lockName.intern()) { // 使用字符串作为锁对象
long acquiredTime = System.nanoTime();
long waitTime = acquiredTime - startTime;

// 记录等待时间
waitTimes.computeIfAbsent(lockName, k -> new AtomicLong())
.addAndGet(waitTime);

// 记录获取次数
lockMetrics.computeIfAbsent(lockName, k -> new AtomicLong())
.incrementAndGet();

// 执行任务
task.run();
}

long endTime = System.nanoTime();
long totalTime = endTime - startTime;
System.out.printf("锁 %s 总耗时: %.2f ms%n",
lockName, totalTime / 1_000_000.0);
}

// 打印监控报告
public static void printMonitoringReport() {
System.out.println("\n=== synchronized 监控报告 ===");

for (Map.Entry<String, AtomicLong> entry : lockMetrics.entrySet()) {
String lockName = entry.getKey();
long acquisitions = entry.getValue().get();
long totalWaitTime = waitTimes.get(lockName).get();

double avgWaitTime = acquisitions > 0 ?
(double) totalWaitTime / acquisitions / 1_000_000 : 0;

System.out.printf("锁: %s%n", lockName);
System.out.printf(" 获取次数: %d%n", acquisitions);
System.out.printf(" 总等待时间: %.2f ms%n", totalWaitTime / 1_000_000.0);
System.out.printf(" 平均等待时间: %.2f ms%n", avgWaitTime);
System.out.println();
}
}

public static void main(String[] args) throws InterruptedException {
// 模拟多个线程使用相同的锁
Thread[] threads = new Thread[10];

for (int i = 0; i < threads.length; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 100; j++) {
monitorLock("shared_lock", () -> {
try {
Thread.sleep(threadId % 3 + 1); // 不同的处理时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}, "Worker-" + threadId);
}

// 启动所有线程
for (Thread thread : threads) {
thread.start();
}

// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}

// 打印监控报告
printMonitoringReport();
}
}

🎯 总结

核心要点回顾

  1. synchronized基础:Java的内置同步机制,解决并发三大问题(原子性、可见性、有序性)
  2. 使用方式:修饰实例方法、静态方法、同步代码块
  3. 锁的类型:对象锁(实例级别)、类锁(全局级别)
  4. 底层原理:基于Monitor机制,通过对象头管理锁状态
  5. 锁升级过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  6. 内存语义:happens-before关系,内存屏障保证可见性
  7. 性能优化:偏向锁、轻量级锁、自旋、锁消除、锁粗化
  8. 最佳实践:最小化同步范围、使用私有锁对象、避免死锁

面试必背清单

  • synchronized的三种使用方式和区别
  • 对象锁vs类锁的区别和互斥关系
  • synchronized的实现原理和Monitor机制
  • 锁升级的详细过程和条件
  • 偏向锁、轻量级锁、重量级锁的区别
  • synchronized vs ReentrantLock的详细对比
  • synchronized的内存语义和可见性保证
  • 常见的synchronized陷阱和最佳实践
  • 如何避免死锁和性能问题
  • JVM的synchronized优化技术

进阶学习路径

  1. 深入源码:阅读HotSpot JVM中ObjectMonitor的源码实现
  2. 并发工具:学习ReentrantLock、ReadWriteLock、StampedLock等
  3. 无锁编程:掌握CAS、原子类、Lock-Free数据结构
  4. 并发框架:了解Disruptor、Akka等高性能并发框架
  5. 性能调优:学习JVM参数调优、并发性能测试
  6. 分布式锁:了解Redis、Zookeeper等分布式锁实现

💡 记住:synchronized是Java并发编程的基础,理解其原理和最佳实践对编写高质量的并发程序至关重要。在大多数场景下,经过优化的synchronized都能提供良好的性能,但在高并发、低延迟的场景下,可以考虑使用更高级的并发工具。