1. 锁的获取与释放方式
- synchronized:
- 它是基于JVM层面的内置锁,通过字节码指令
monitorenter
和 monitorexit
实现。当一个线程进入同步块时,会自动获取锁,退出同步块(正常结束或异常)时自动释放锁。
- 例如:
public class SynchronizedExample {
private static final Object lock = new Object();
public void synchronizedMethod() {
synchronized (lock) {
// 同步代码块
}
}
}
- ReentrantLock:
- 它是基于API层面的锁,通过调用
lock()
方法获取锁,调用 unlock()
方法释放锁。需要手动在 finally
块中释放锁,以确保在异常情况下也能正确释放锁,避免死锁。
- 例如:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static final ReentrantLock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try {
// 同步代码块
} finally {
lock.unlock();
}
}
}
2. 锁的公平性
- synchronized:
- 是非公平锁。线程在竞争锁时,不考虑等待时间的长短,只要锁一释放,任何线程都有机会获取锁,可能导致某些线程长时间等待。
- ReentrantLock:
- 可以通过构造函数指定是否为公平锁。默认构造函数创建的是非公平锁。公平锁按照线程等待的先后顺序分配锁,能减少线程饥饿现象,但性能比非公平锁低,因为公平锁需要维护一个等待队列,线程切换开销更大。
- 例如,创建公平锁:
ReentrantLock fairLock = new ReentrantLock(true);
3. 锁的可中断性
- synchronized:
- 不可中断。当一个线程获取到锁后,其他线程只能等待,无法在等待过程中被中断。
- ReentrantLock:
- 可中断。通过
lockInterruptibly()
方法获取锁时,如果当前线程在等待过程中被中断,会抛出 InterruptedException
异常并放弃获取锁。
- 例如:
try {
lock.lockInterruptibly();
// 同步代码块
} catch (InterruptedException e) {
// 处理中断
} finally {
lock.unlock();
}
4. 锁的绑定条件
- synchronized:
- 与一个对象实例绑定,每个对象都有一个监视器(monitor),通过
synchronized
关键字修饰的代码块或方法,实际上是获取该对象的监视器锁。
- ReentrantLock:
- 可以绑定多个
Condition
对象,每个 Condition
可以实现不同的等待和唤醒策略。Condition
比 Object
的 wait()
和 notify()
方法更灵活,可实现更细粒度的线程间通信。
- 例如:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
try {
lock.lock();
condition.await();
} catch (InterruptedException e) {
// 处理中断
} finally {
lock.unlock();
}
场景选择
- 优先选择synchronized的场景:
- 代码简单,对锁的功能要求不高,主要关注代码的简洁性和可读性。例如,简单的同步方法或同步块来保证数据的一致性。
- 不需要考虑锁的公平性、可中断性等特殊需求时,因为
synchronized
性能优化后,在一般场景下能满足并发需求且开销较小。
- 优先选择ReentrantLock的场景:
- 需要实现公平锁机制,避免线程长时间等待,例如在一些对公平性要求较高的资源分配场景。
- 需要锁的可中断性,当线程在等待锁的过程中需要能够响应中断信号时,如在处理任务取消的场景。
- 需要更灵活的线程间通信,通过
Condition
实现比 synchronized
更细粒度的控制,例如在生产者 - 消费者模型中实现更复杂的唤醒策略。