一句话总结:
synchronized 是“傻瓜式自动锁”,简单但功能有限;Lock 是“手动挡高级锁”,灵活但需细心操作。
一、核心区别对比
| 对比项 | synchronized | Lock(如ReentrantLock) |
|---|---|---|
| 锁获取方式 | 自动获取与释放(代码块结束) | 手动 lock() 和 unlock() |
| 中断响应 | ❌ 不可中断 | ✅ 支持 lockInterruptibly() |
| 公平性 | ❌ 非公平锁(默认) | ✅ 可设置为公平锁 |
| 条件变量 | 仅一个等待队列(wait()/notify()) | ✅ 支持多个 Condition |
| 性能 | JDK优化后与Lock接近,低竞争下更优 | 高竞争场景更灵活 |
| 锁状态 | 无法查看锁是否被占用 | ✅ 可查询锁状态(tryLock()) |
| 适用场景 | 简单同步需求 | 复杂同步控制(如超时、公平性) |
二、具体差异详解
1. 锁的获取与释放
-
synchronized:
-
自动管理:进入同步代码块自动加锁,退出时自动释放。
-
风险:若代码异常退出,锁会自动释放,避免死锁。
synchronized (obj) { // 自动加锁 // ... } // 自动释放锁 -
-
Lock:
-
手动控制:必须显式调用
lock()和unlock(),通常放在try-finally中确保释放。 -
风险:忘记
unlock()会导致死锁!
Lock lock = new ReentrantLock(); lock.lock(); try { // ... } finally { lock.unlock(); // 必须手动释放 } -
2. 中断与超时控制
-
synchronized:
-
线程在等待锁时 无法被中断,只能死等。
synchronized (obj) { // 线程会一直阻塞,直到获取锁 } -
-
Lock:
- 可中断等待:通过
lockInterruptibly()允许其他线程中断等待。 - 超时获取:
tryLock(5, TimeUnit.SECONDS)尝试5秒,超时放弃。
if (lock.tryLock(3, TimeUnit.SECONDS)) { try { /* ... */ } finally { lock.unlock(); } } else { // 超时未获取锁,执行其他逻辑 } - 可中断等待:通过
3. 公平性
-
synchronized:
- 非公平锁:谁抢到锁谁用,可能导致线程“饥饿”。
-
Lock:
- 可设公平锁:按请求顺序分配锁(但性能略低)。
Lock fairLock = new ReentrantLock(true); // 公平锁
4. 条件变量(Condition)
-
synchronized:
-
只能通过
wait()和notify()控制一个等待队列。
synchronized (obj) { while (条件不满足) { obj.wait(); // 所有线程在同一个队列等待 } } -
-
Lock:
-
支持多个
Condition,实现精细控制(如生产者-消费者模型)。
Lock lock = new ReentrantLock(); Condition notFull = lock.newCondition(); // 队列未满条件 Condition notEmpty = lock.newCondition(); // 队列非空条件 // 生产者:队列满时等待 notFull public void produce() { lock.lock(); try { while (queue.isFull()) { notFull.await(); } queue.add(item); notEmpty.signal(); // 唤醒消费者 } finally { lock.unlock(); } } -
三、使用场景建议
| 场景 | 推荐选择 | 理由 |
|---|---|---|
| 简单同步(如计数器) | synchronized | 代码简洁,自动管理锁 |
| 需要超时或中断 | Lock | 灵活控制锁的获取与释放 |
| 公平锁需求 | Lock | 支持按请求顺序分配锁 |
| 复杂线程协作 | Lock + Condition | 多条件队列实现精细控制 |
四、总结
- synchronized:适合简单场景,避免手动管理锁的复杂性。
- Lock:适合高并发、复杂同步逻辑,提供更多控制权。
口诀:
「synchronized 自动挡,简单场景它最强
Lock 手动更灵活,超时中断公平锁
按需选择两兄弟,代码安全又高效!」