1. 基本特性对比
| 特性 | synchronized | Lock (如 ReentrantLock) |
|---|---|---|
| 实现方式 | Java 关键字,JVM 内置支持 | Java 接口,需要显式创建对象 |
| 锁获取 | 自动获取和释放锁 | 需要显式调用 lock() 和 unlock() |
| 灵活性 | 较低,结构固定 | 较高,可控制锁的获取时机 |
| 性能 | 早期版本较差,JDK 1.6 后大幅优化 | 通常性能更好,尤其在高竞争场景 |
2. 主要区别详解
2.1 使用方式
// synchronized 使用
public synchronized void syncMethod() {
// 方法体
}
public void syncBlock() {
synchronized(this) {
// 同步代码块
}
}
// Lock 使用
private final Lock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock(); // 确保锁释放
}
}
2.2 可中断性
// synchronized 不可中断
synchronized void syncMethod() {
// 如果线程在等待锁,无法被中断
}
// Lock 支持可中断获取
lock.lockInterruptibly(); // 等待时可响应中断
try {
// ...
} finally {
lock.unlock();
}
2.3 尝试获取锁
// synchronized 只能阻塞等待
synchronized(obj) {
// 拿不到锁就一直等
}
// Lock 可以尝试获取
if (lock.tryLock()) { // 立即返回结果
try {
// 获取锁成功
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他逻辑
}
// 带超时的尝试
if (lock.tryLock(3, TimeUnit.SECONDS)) {
// ...
}
2.4 公平性
// synchronized 是非公平锁
synchronized(obj) { ... }
// Lock 可以创建公平锁
Lock fairLock = new ReentrantLock(true); // true 表示公平锁
2.5 条件变量
// synchronized 配合 wait/notify
synchronized(lockObj) {
while (!condition) {
lockObj.wait();
}
// 执行业务
lockObj.notifyAll();
}
// Lock 使用 Condition
private final Condition condition = lock.newCondition();
lock.lock();
try {
while (!conditionMet) {
condition.await(); // 等待条件
}
// 执行业务
condition.signal(); // 唤醒一个等待线程
} finally {
lock.unlock();
}
3. 选择建议
使用 synchronized 的场景:
- 简单的同步需求
- 代码量少,希望减少复杂性
- 锁持有时间短,竞争不激烈
- 需要自动管理锁的获取和释放
使用 Lock 的场景:
- 需要可中断的锁获取
- 需要尝试获取锁(非阻塞或超时等待)
- 需要公平锁策略
- 需要多个条件变量
- 锁需要在多个方法间传递
- 需要更细粒度的锁控制
4. 性能考虑
- JDK 1.5 之前:synchronized 性能较差
- JDK 1.6 及以后:synchronized 引入了锁升级机制(偏向锁→轻量级锁→重量级锁),性能大幅提升
- 高竞争场景:Lock 通常表现更好
- 低竞争场景:synchronized 性能足够且更简单
5. 注意事项
- Lock 必须手动释放:必须在 finally 块中调用 unlock()
- synchronized 的锁范围:可以修饰方法或代码块
- 可重入性:两者都支持可重入
- 死锁风险:Lock 使用不当更容易导致死锁
总结
synchronized是 Java 内置的简单同步机制,适合大多数基本同步需求。Lock接口提供了更丰富的功能和控制能力,适合复杂的同步场景。选择时需根据具体需求、性能要求和代码复杂度综合考虑。