synchronized的高频面试题以及答案
以下是关于Java中`synchronized`关键字的高频面试题及答案解析,结合原理、使用场景和底层实现进行归纳:
---
一、基本概念与作用
1. `synchronized`的作用是什么?
`synchronized`是Java的关键字,用于实现线程同步,确保同一时间只有一个线程能执行被保护的代码块或方法,解决多线程环境下的共享资源竞争问题。它提供了互斥性(原子性)和可见性,并间接保证有序性。
- 互斥性:防止多个线程同时修改共享变量。
- 可见性:线程释放锁时将变量刷新到主内存,获取锁时重新加载最新值。
- 有序性:通过锁规则禁止指令重排序。
2. `synchronized`的三种使用方式及锁对象?
| 修饰方式 | 锁对象 | 作用范围 |
|--------------------|--------------------------------|----------------------------------|
| 修饰实例方法 | 当前实例对象(`this`) | 同一实例的同步方法/代码块互斥执行 |
| 修饰静态方法 | 类的`Class`对象 | 所有实例共享同一锁,全局互斥 |
| 修饰代码块 | 括号内指定的对象 | 灵活控制锁范围(如静态成员变量) |
示例:
```java
// 实例方法锁(this)
public synchronized void method() {}
// 静态方法锁(Class对象)
public static synchronized void staticMethod() {}
// 代码块锁(指定对象)
synchronized (lockObject) { ... }
```
---
二、底层实现与锁升级
3. `synchronized`的底层实现原理?
- Monitor机制:每个Java对象关联一个监视器(`ObjectMonitor`),通过`monitorenter`(加锁)和`monitorexit`(解锁)指令实现。
- 对象头(Mark Word):存储锁状态信息,包括哈希码、分代年龄、锁标志位等。锁状态变化时,Mark Word动态调整。
- 锁升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆):
- 偏向锁:适用于单线程重复获取锁的场景,记录线程ID,后续无需CAS操作。
- 轻量级锁:通过CAS自旋尝试获取锁,适用于低竞争场景。
- 重量级锁:依赖操作系统互斥量(Mutex),线程阻塞等待,适用于高并发场景。
4. 为什么调用`wait()`/`notify()`需要` synchronized`锁?
- `wait()`/`notify()`操作依赖对象的监视器锁。调用前需获取锁,否则会抛出`IllegalMonitorStateException`。
- 底层原理:`wait()`释放锁并将线程放入`WaitSet`;`notify()`唤醒`WaitSet`中的线程,使其重新竞争锁。
---
三、与其他同步机制的对比
5. `synchronized`与`ReentrantLock`的区别?
| 特性 | `synchronized` | `ReentrantLock` |
|------------------|---------------------------------|----------------------------------|
| 实现层面 | JVM层面(字节码指令) | API层面(`java.util.concurrent`) |
| 锁释放 | 自动释放 | 需手动`unlock()` |
| 公平性 | 非公平锁 | 支持公平/非公平模式 |
| 中断响应 | 不支持 | 支持(`lockInterruptibly()`) |
| 条件变量 | 单一等待队列(`wait/notify`) | 多个`Condition`队列 |
| 适用场景 | 简单同步逻辑 | 复杂并发控制(如超时、精确唤醒) |
6. `synchronized`与`volatile`的区别?
| 特性 | `synchronized` | `volatile` |
|------------------|---------------------------------|---------------------------------|
| 原子性 | 是(保证复合操作原子性) | 否(仅保证单次读写原子性) |
| 可见性 | 是 | 是(强制刷新主内存) |
| 禁止重排序 | 是(通过锁规则) | 是(插入内存屏障) |
| 适用场景 | 多线程共享资源修改 | 状态标志、单写多读场景 |
---
四、常见陷阱与优化
7. 锁对象选择不当导致线程不安全?
- 错误示例:以`Integer`等包装类作为锁对象,因自动装箱可能创建新对象,导致锁失效。
- 优化建议:使用`final`静态对象或私有锁对象,避免`this`或类对象被意外共享。
8. 如何避免死锁?
- 固定锁顺序:按对象哈希码或唯一标识排序获取锁。
- 减少锁粒度:拆分大锁为小锁(如分段锁)。
- 使用超时机制:`tryLock()`避免无限等待(仅`ReentrantLock`支持)。
---
五、高频原理题
9. 为什么`wait()`后需要`notify()`唤醒?
`wait()`释放锁后,线程进入`WaitSet`等待;`notify()`将其移回竞争队列(`EntryList`),但需重新获取锁才能继续执行。
10. 锁升级的触发条件?
- 偏向锁 → 轻量级锁:其他线程尝试获取锁时。
- 轻量级锁 → 重量级锁:自旋次数超限(默认10次)或竞争线程超过CPU一半。
---
总结
`synchronized`是Java并发编程的核心机制,需掌握其锁对象选择、锁升级过程及与ReentrantLock/volatile的差异。实际开发中,优先使用`volatile`或原子类优化性能,高并发场景可结合`ReentrantLock`实现复杂控制。更多细节可参考[Java并发编程实战]或HotSpot源码分析。