线程安全与synchronized
一、线程安全基础:竞态条件 & 临界区
1. 竞态条件(Race Condition)
多个线程同时操作、修改同一个共享变量,执行结果依赖线程执行的先后顺序,最终导致结果错误。
- 根本原因:读 - 改 - 写三步不是原子操作。
- 典型例子:
i++多线程下计数错误。
2. 临界区(Critical Section)
会发生线程安全问题的代码块,就是临界区。
- 多个线程同时进入临界区 → 线程不安全。
- 同一时间只允许一个线程进入 → 线程安全。
二、synchronized 三种使用方式
synchronized 是独占互斥锁,保证同一时刻只有一个线程进入临界区。
1. 修饰实例方法(锁当前对象 this)
1 | |
锁对象:this(当前实例对象)
2. 修饰静态方法(锁当前类 Class 对象)
1 | |
锁对象:当前类.class(全局唯一)
3. 修饰代码块(灵活指定锁对象)
1 | |
锁对象:可以是任意对象(推荐用 private final Object lock = new Object())
三、synchronized 三大锁特性
1. 互斥性(Mutex)
同一时刻只有一个线程能持有锁,其他线程阻塞等待。
→ 保证原子性。
2. 可重入性(Reentrant)
同一个线程可以再次获取自己已经持有的锁,不会死锁。
- 底层:锁关联计数器,获取 + 1,释放 - 1,为 0 才真正释放。
- 作用:避免同一个线程多次调用同步方法时死锁。
3. 内存语义(Memory Semantics)
保证:
- 可见性:解锁前把变量刷回主内存,加锁时重新读取主内存。
- 有序性:禁止临界区内外代码重排。
临界区内部可以重排;临界区外部代码,不能重排到临界区内部;临界区内部代码,也不能重排到外部。
- 原子性:整块代码串行执行。
四、锁升级流程(重点!面试必考)
JDK 1.6 以后为了优化性能,synchronized 不再一上来就是重量级锁,而是逐步升级:
1 | |
只会升级,不会降级。
1. 偏向锁(Biased Lock)
- 场景:只有一个线程反复获取锁,无竞争。
- 原理:锁对象标记当前线程 ID,下次直接进,无 CAS 操作。
- 特点:开销极低。
2. 轻量级锁(Lightweight Lock)
- 场景:少量线程竞争,交替获取锁(无长时间阻塞)。
- 原理:使用CAS + 自旋尝试获取锁,不阻塞线程。
- 特点:避免线程阻塞 / 唤醒的开销,消耗 CPU。
3. 重量级锁(Heavyweight Lock)
- 场景:高竞争、线程长时间占用锁。
- 原理:内核态互斥锁,线程阻塞进入等待队列。
- 特点:不消耗 CPU,但线程切换开销大。
五、synchronized 底层:Monitor 机制
1. 什么是 Monitor?
Monitor 叫管程 / 监视器,是 JVM 底层实现的同步机制(C++ 实现)。
每个对象天生关联一个 Monitor。
2. 核心结构
- _owner:持有锁的线程
- _WaitSet:wait () 等待队列
- _EntryList:阻塞等待队列
3. 加锁 / 解锁流程
- 线程进入同步块 → 尝试获取 Monitor。
- 若未被持有 → _owner 指向当前线程,计数器 + 1。
- 若已被持有 → 进入 _EntryList 阻塞。
- 线程 wait () → 释放锁,进入 _WaitSet。
- 执行完毕 → 释放锁,唤醒等待线程。
4. 底层指令
- 加锁:
monitorenter - 解锁:
monitorexit(自动生成,异常也会保证解锁)
线程安全与synchronized
http://hanqichuan.com/2026/04/15/java并发/线程安全与synchronized/