Java锁:Lock与synchronized的全面对比
1. 基本概念
synchronized
synchronized
是Java内置的关键字,是最基本的线程同步机制,它可以用于方法或代码块:
// 同步方法
public synchronized void method() {
// ...
}
// 同步代码块
public void method() {
synchronized(this) {
// ...
}
}
原理图
Lock接口
Lock
是Java 5引入的显式锁接口,位于java.util.concurrent.locks
包中,主要实现类是ReentrantLock
:
Lock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
}
2. 主要区别
特性 | synchronized | Lock |
---|---|---|
实现方式 | JVM内置关键字 | Java类实现 |
获取与释放 | 自动获取和释放 | 需要显式调用lock()和unlock() |
尝试非阻塞获取锁 | 不支持 | 支持tryLock() |
可中断性 | 不支持中断 | 支持lockInterruptibly() |
公平性 | 非公平 | 可配置公平或非公平 |
条件变量 | 一个隐式条件(通过wait/notify) | 可创建多个Condition对象 |
性能 | 早期版本性能较差,后续优化 | 通常性能更好 |
代码复杂度 | 简单 | 更复杂,需要确保释放锁 |
3. 优缺点分析
synchronized的优点
- 使用简单:语法简洁,无需手动释放锁
- 自动释放:在代码块或方法执行完毕后自动释放锁
- JVM优化:JVM会对synchronized进行优化,如锁消除、锁粗化、偏向锁、轻量级锁等
- 内置特性:与wait()/notify()机制天然集成
synchronized的缺点
- 功能有限:无法实现尝试获取锁、定时获取锁、可中断获取锁等高级功能
- 不可中断:一旦进入阻塞状态,无法被中断
- 单一条件:只能有一个隐式的等待条件
- 非公平性:锁获取总是非公平的
Lock的优点
- 功能丰富:支持尝试获取、定时获取、可中断获取等
- 公平性可选:可以创建公平锁
- 多条件变量:可以创建多个Condition对象
- 性能优势:在高竞争环境下通常表现更好
- 可扩展性:可以基于Lock接口实现更复杂的同步机制
Lock的缺点
- 使用复杂:需要手动获取和释放锁,容易忘记释放
- 编码要求高:通常需要在finally块中释放锁
- 无自动优化:没有JVM内置的优化机制
4. 使用场景
适合使用synchronized的场景
- 简单的同步需求:只需要基本的互斥访问控制
- 锁持有时间短:锁的持有时间很短,竞争不激烈
- 代码简洁优先:希望代码尽可能简洁明了
- 与wait/notify配合:需要使用内置的等待/通知机制
适合使用Lock的场景
- 需要高级功能:如尝试获取锁、定时获取锁、可中断获取锁等
- 公平性要求:需要公平的锁获取机制
- 多条件变量:需要多个等待条件
- 高竞争环境:锁竞争激烈,需要更好的性能
- 跨越方法边界:需要在不同方法中获取和释放锁
5. 性能考虑
在Java 6及以后版本中,synchronized经过大量优化,在低竞争环境下性能与Lock相当。但在高竞争环境下,Lock通常表现更好,特别是:
- 当使用
ReentrantLock
的公平模式时 - 当需要尝试获取锁(tryLock)时
- 当锁竞争非常激烈时
6. 最佳实践
- 优先考虑synchronized:对于简单场景,优先使用synchronized,它更简单且不易出错
- 需要高级功能时使用Lock:当synchronized无法满足需求时再考虑Lock
- 确保释放Lock:使用try-finally确保Lock被释放
- 避免过早优化:不要为了可能的性能提升而过度使用Lock
- 考虑读写锁:读多写少场景考虑使用
ReentrantReadWriteLock
7. 示例代码对比
基本同步
// synchronized
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
}
// Lock
public class Counter {
private int count;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
尝试获取锁
// 只有Lock支持
public boolean tryIncrement() {
if (lock.tryLock()) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false;
}
条件变量
// synchronized - 单一条件
synchronized(lockObj) {
while (!condition) {
lockObj.wait();
}
// ...
lockObj.notifyAll();
}
// Lock - 多条件
Condition condition = lock.newCondition();
lock.lock();
try {
while (!someCondition) {
condition.await();
}
// ...
condition.signal();
} finally {
lock.unlock();
}
结论
synchronized
和Lock
各有优劣,选择哪种锁机制取决于具体需求。对于大多数简单场景,synchronized
是更安全、更简洁的选择。当需要更高级的同步功能时,Lock
提供了更大的灵活性和控制能力。理解两者的区别和适用场景,可以帮助开发者编写出更高效、更健壮的并发代码。