在 Java 多线程编程中,synchronized
和 Lock
(如 ReentrantLock
)是两种常见的线程同步机制。以下是它们的核心区别和典型使用场景,结合代码示例说明:
一、synchronized 的常见场景
1. 简单的临界区保护
public class Counter {
private int count = 0;
// synchronized 修饰方法(锁对象为当前实例)
public synchronized void increment() {
count++;
}
// synchronized 代码块(显式指定锁对象)
public void decrement() {
synchronized(this) {
count--;
}
}
}
适用场景:
- 快速实现线程安全的单例模式、计数器等简单同步逻辑
- 不需要手动释放锁,避免锁泄漏风险
2. 类级别锁
public class Logger {
// 锁对象为 Class 对象
public static synchronized void log(String message) {
System.out.println(Thread.currentThread().getName() + ": " + message);
}
}
适用场景:
- 需要全局唯一的锁(如静态方法同步)
3. 对象内部状态保护
public class BankAccount {
private double balance;
public synchronized void transfer(BankAccount target, double amount) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
}
}
适用场景:
- 保护对象内部状态的原子性操作(如账户转账)
二、Lock 的常见场景
1. 非阻塞尝试获取锁
public class Cache {
private final ReentrantLock lock = new ReentrantLock();
private Map<String, Object> data = new HashMap<>();
public boolean tryUpdate(String key, Object value) {
if (lock.tryLock()) { // 尝试获取锁,失败立即返回
try {
data.put(key, value);
return true;
} finally {
lock.unlock();
}
}
return false;
}
}
适用场景:
- 高并发下避免线程阻塞(如缓存更新、限流逻辑)
2. 可中断的锁等待
public class TaskQueue {
private final ReentrantLock lock = new ReentrantLock();
private Queue<Runnable> queue = new LinkedList<>();
public Runnable getTask() throws InterruptedException {
lock.lockInterruptibly(); // 允许被中断的锁等待
try {
while (queue.isEmpty()) {
Condition notEmpty = lock.newCondition();
notEmpty.await();
}
return queue.poll();
} finally {
lock.unlock();
}
}
}
适用场景:
- 需要响应中断的任务调度(如线程池任务队列)
3. 公平锁
public class TicketSystem {
// 公平锁(按请求顺序分配锁)
private final ReentrantLock lock = new ReentrantLock(true);
private int tickets = 100;
public void buyTicket() {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " buys ticket " + tickets--);
}
} finally {
lock.unlock();
}
}
}
适用场景:
- 需要避免线程饥饿(如票务系统、资源按顺序分配)
4. 读写锁分离
public class ConfigManager {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock private Map<String, String> config = new HashMap<>();
// 读操作(共享锁)
public String getConfig(String key) {
rwLock.readLock().lock();
try {
return config.get(key);
} finally {
rwLock.readLock().unlock();
}
}
// 写操作(独占锁)
public void updateConfig(String key, String value) {
rwLock.writeLock().lock();
try {
config.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
适用场景:
- 读多写少的场景(如配置管理、缓存系统)
三、如何选择?
特性 | synchronized | Lock |
---|---|---|
锁释放 | 自动释放(代码块/方法退出时) | 必须手动调用 unlock() |
可中断性 | 不支持 | 支持 (lockInterruptibly() ) |
超时机制 | 不支持 | 支持 (tryLock(long, TimeUnit) ) |
公平锁 | 非公平锁(默认) | 可配置公平锁 |
锁绑定多个条件 | 不支持 | 支持 (newCondition() ) |
性能 | JDK6 后优化较好(偏向锁/轻量级锁) | 高竞争场景下性能更优 |
四、高频面试点
- 锁升级过程(偏向锁 → 轻量级锁 → 重量级锁)
- synchronized 的底层实现(对象头 Mark Word、Monitor 机制)
- AQS(AbstractQueuedSynchronizer)原理(Lock 的底层实现)
- 死锁避免方法(如按固定顺序获取锁、使用
tryLock
) - 锁粒度优化(如缩小同步代码块范围)
五、陷阱提醒
- ❌ 在循环中调用
tryLock()
时未设置超时,可能导致 CPU 空转 - ❌ 忘记在
finally
块中释放Lock
,造成锁泄漏 - ❌ 错误使用
synchronized
修饰静态方法和实例方法,导致锁对象混淆
掌握这些核心场景和底层原理,可以更好地回答面试中的锁相关问题。