synchronized 为什么能保证线程安全?
面试频率:★★★★★
工作频率:★★★★★
⚡30 秒速记(复习只看这里)
🟢 一句话
synchronized 是 Java 提供的同步锁机制,通过保证同一时间只有一个线程执行临界区代码,从而保证线程安全。
🟡 synchronized 三大作用
1. 原子性 ✅
多个操作:
只能一个线程执行
例如:
count++;
不会被其他线程打断。
2. 可见性 ✅
线程释放锁:
修改的数据刷新到主内存
其他线程获取锁:
读取最新数据
3. 有序性 ✅
禁止锁内代码发生不合理重排序。
🟠 synchronized 锁什么?
实例方法
public synchronized void test(){
}
锁:
当前对象 this
静态方法
public static synchronized void test(){
}
锁:
Class对象
例如:
UserService.class
代码块
synchronized(obj){
}
锁:
obj对象
🔴 一句话
synchronized 锁的是对象,不是方法。
📚 完整知识
一、为什么需要 synchronized?
看代码:
public class Counter {
private int count = 0;
public void add(){
count++;
}
}
两个线程:
线程A:
count++
线程B:
count++
看起来:
count = 2
但是实际:
读取 count
↓
增加1
↓
写回
不是一个操作。
可能:
线程A:
读取 count=0
线程B:
读取 count=0
线程A:
写入1
线程B:
写入1
最终:
count=1
发生:
数据丢失。
二、synchronized 如何解决?
加锁:
public synchronized void add(){
count++;
}
执行:
线程A
↓
获取锁
↓
执行代码
↓
释放锁
线程B
↓
等待
↓
获取锁
↓
执行代码
最终:
count=2
三、什么是临界区?
临界区:
多线程同时访问可能产生问题的代码区域。
例如:
count++;
就是临界区。
synchronized:
作用:
保护临界区
保证:
同一时间只有一个线程进入。
四、synchronized 锁对象详解
1. 实例方法
代码:
public synchronized void save(){
}
等价于:
public void save(){
synchronized(this){
}
}
锁:
当前对象
例如:
UserService service = new UserService();
锁:
service对象
2. 静态方法
代码:
public static synchronized void save(){
}
锁:
UserService.class
因为:
静态方法属于类。
3. synchronized代码块
代码:
synchronized(lock){
}
锁:
lock对象
五、为什么不同对象不会互相阻塞?
例如:
对象A:
锁A
对象B:
锁B
两个线程:
线程1 → 对象A
线程2 → 对象B
可以同时执行。
因为:
锁不同。
六、synchronized 底层原理
Java对象:
都有:
对象头
其中包含:
Mark Word
里面记录:
锁状态
线程信息
加锁过程:
线程竞争
↓
修改对象头
↓
记录锁状态
↓
获得锁
七、synchronized 锁升级
JDK1.6 后:
synchronized 性能优化。
锁状态:
无锁
↓
偏向锁
↓
轻量级锁
↓
重量级锁
八、无锁
没有线程竞争。
例如:
synchronized(obj){
}
第一次进入。
直接执行。
九、偏向锁
场景:
只有一个线程访问。
例如:
线程A
↓
多次进入同一个锁
JVM:
记录:
哪个线程拥有锁
避免重复竞争。
十、轻量级锁
场景:
竞争不激烈。
多个线程尝试获取。
使用:
CAS
尝试获取锁。
十一、重量级锁
竞争严重。
线程无法获取锁。
进入:
阻塞等待
需要操作系统帮助调度。
十二、synchronized 和 volatile 区别
| synchronized | volatile | |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 原子性 | ✅ | ❌ |
| 有序性 | ✅ | ✅ |
| 锁 | ✅ | ❌ |
| 性能 | 相对低 | 高 |
| 适合场景 | 复杂同步 | 状态通知 |
十三、为什么 volatile 不能替代 synchronized?
例如:
volatile int count;
count++;
volatile:
只能保证:
别人能看到最新值
但是:
count++:
包含:
读取
↓
计算
↓
写入
多个步骤。
仍然可能被打断。
synchronized:
保证:
整个过程不可被其他线程插入
十四、ConcurrentHashMap 为什么还需要 synchronized?
你之前学过:
ConcurrentHashMap:
CAS
+
synchronized
为什么不用 volatile?
因为:
volatile 只能保证:
变量可见
例如:
table[index]
引用变化。
但是修改链表:
Node.next修改
节点插入
红黑树调整
涉及多个操作。
需要:
synchronized
保证整体安全。
💼 工作场景
场景:订单数量统计
错误:
private int count;
public void add(){
count++;
}
多个请求:
线程A
线程B
线程C
同时修改。
可能:
数据错误
正确:
public synchronized void add(){
count++;
}
🎤 面试回答(30秒)
synchronized 是 Java 提供的同步机制,通过对象锁保证同一时间只有一个线程执行临界区代码,从而保证线程安全。它可以保证原子性、可见性和有序性。synchronized 锁的是对象,例如实例方法锁当前对象,静态方法锁 Class 对象,代码块锁指定对象。JDK1.6 后对 synchronized 做了优化,引入偏向锁、轻量级锁和重量级锁,提高性能。
🎯 面试追问
Q1:synchronized 锁的是方法吗?
答:
不是。
锁的是对象。
Q2:两个 synchronized 方法一定互斥吗?
答:
不一定。
如果锁的对象不同,不会互相阻塞。
Q3:volatile 能替代 synchronized 吗?
答:
不能。
volatile 不保证原子性。
Q4:ConcurrentHashMap 为什么不用一把 synchronized?
答:
锁粒度太大,影响并发性能。
Q5:synchronized 和 CAS 区别?
答:
CAS 是乐观锁,通过比较交换尝试修改。
synchronized 是悲观锁,没有获取锁的线程等待。
⚠️ 工作踩坑
1. synchronized 加在错误对象上
例如:
synchronized(new Object()){
}
每次都是新对象。
等于没有锁。
2. 认为 synchronized 方法锁整个类
错误:
实例方法锁对象。
静态方法锁 Class。
3. 认为 volatile 和 synchronized 一样
错误:
volatile 不保证原子性。
❓ 自测
- synchronized 为什么能保证线程安全?
- synchronized 锁的是什么?
- 实例方法和静态方法锁有什么区别?
- 为什么 count++ 需要锁?
- volatile 为什么不能替代 synchronized?
- synchronized 锁升级过程是什么?
- ConcurrentHashMap 为什么使用 synchronized?
- CAS 和 synchronized 有什么区别?
无锁
↓
偏向锁
(只有一个线程)
↓
轻量级锁
(少量竞争,CAS)
↓
重量级锁
(大量竞争,阻塞)