hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试官:详细说说synchronized
synchronized
是 Java 中实现线程同步的核心关键字,用于解决多线程环境下的资源竞争问题,确保线程安全。
1. 基本作用
synchronized
通过 互斥锁(Mutex Lock) 机制,保证同一时刻只有一个线程能访问被保护的代码块或方法,避免多个线程同时操作共享资源导致的数据不一致问题。
2. 使用方式
synchronized
可以修饰以下三种对象:
(1) 实例方法
-
锁对象 :当前实例对象(
this
)。 -
作用 :同一实例的多个线程会互斥访问该方法。
javapublic synchronized void method() { // 同步代码 }
(2) 静态方法
-
锁对象 :类的
Class
对象(MyClass.class
)。 -
作用 :所有实例的线程都会互斥访问该静态方法。
javapublic static synchronized void staticMethod() { // 同步代码 }
(3) 代码块
-
锁对象:显式指定任意对象(通常是共享资源)。
-
作用 :更细粒度地控制同步范围。
javapublic void blockMethod() { synchronized (lockObject) { // lockObject 可以是任意对象 // 同步代码 } }
3. 核心特性
(1) 可重入性(Reentrant)
- 同一个线程可以重复获取同一个锁。
- 示例:线程获取锁后,在同步代码中调用另一个同步方法(使用同一锁)不会阻塞。
(2) 锁的释放
- 线程执行完同步代码块或方法后,自动释放锁。
- 若线程发生异常退出同步代码块,锁也会自动释放。
(3) 锁的竞争
- 未获取锁的线程会进入 阻塞状态(BLOCKED),直到锁被释放。
4. 底层原理
synchronized
的底层实现依赖于 JVM 的 Monitor(监视器锁) 机制,具体通过以下步骤实现:
(1) Monitor 对象
- 每个 Java 对象都与一个 Monitor 关联,Monitor 包含以下关键字段:
_owner
:记录当前持有锁的线程。_EntryList
:等待获取锁的阻塞线程队列。_WaitSet
:调用wait()
后进入等待状态的线程队列。
(2) 字节码层面
-
同步代码块通过
monitorenter
和monitorexit
指令实现:javapublic void method() { synchronized (obj) { // 代码 } }
对应的字节码:
monitorenter // 尝试获取锁 ... // 同步代码 monitorexit // 释放锁
(3) 锁升级优化(JDK 1.6+)
为了提高性能,JVM 对 synchronized
进行了优化,引入了 锁升级机制:
- 无锁(No Lock):初始状态。
- 偏向锁(Biased Lock):锁偏向第一个获取它的线程,避免后续 CAS 操作。
- 轻量级锁(Lightweight Lock):通过 CAS 自旋尝试获取锁,避免线程阻塞。
- 重量级锁(Heavyweight Lock):竞争激烈时,升级为操作系统级别的互斥锁(Mutex)。
5. 典型应用场景
(1) 线程安全的单例模式
java
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
(2) 保护共享资源
java
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
6. 与 ReentrantLock
的对比
特性 | synchronized |
ReentrantLock |
---|---|---|
锁类型 | JVM 内置锁 | JDK 实现的显式锁 |
灵活性 | 不支持中断、超时、公平锁 | 支持中断、超时、公平锁 |
代码控制 | 自动释放锁 | 需手动 lock() 和 unlock() |
性能 | JDK 1.6 后优化,性能接近显式锁 | 高竞争场景下性能更好 |
适用场景 | 简单同步需求 | 复杂同步逻辑(如条件变量 Condition ) |
7. 注意事项
-
锁对象的选择:
- 避免使用字符串常量或基本类型(如
Integer
)作为锁对象。 - 推荐使用私有、不可变的对象:
private final Object lock = new Object();
- 避免使用字符串常量或基本类型(如
-
减少同步范围:
- 尽量缩小同步代码块,避免在同步块内执行耗时操作(如 I/O)。
-
死锁风险:
- 避免嵌套锁或多锁顺序不一致导致的死锁。
- 示例:线程 A 先锁 X 再锁 Y,线程 B 先锁 Y 再锁 X → 可能死锁。
-
锁粗化与锁消除:
- 锁粗化:JVM 将多个连续锁合并为一个锁,减少开销。
- 锁消除:JVM 检测到不可能存在共享竞争时,自动去除锁(如局部变量)。
8. 常见问题
Q1:synchronized
和 volatile
的区别?
synchronized
:保证原子性、可见性和有序性。volatile
:仅保证可见性和有序性,不保证原子性(如i++
仍需同步)。
Q2:静态方法和实例方法的锁是否冲突?
- 不冲突!静态方法锁的是类对象(
Class
),实例方法锁的是当前实例(this
)。
Q3:如何排查死锁?
- 使用
jstack
或可视化工具(如 JConsole)查看线程状态和锁持有情况。
总结
synchronized
是 Java 线程同步的基石,通过 Monitor 机制实现互斥访问。虽然在高并发场景下可能成为性能瓶颈,但其简洁性和 JVM 的优化(如锁升级)使其在大多数场景下足够高效。对于更复杂的同步需求,可结合 ReentrantLock
或并发工具类(如 Semaphore
、CountDownLatch
)使用。
