使用场景
- synchronized:可用于修饰方法和代码块,在普通的同步场景,如简单的对象同步操作中使用较为方便,它是Java内置的关键字,和代码耦合度高。
- ReentrantLock:通常用于更复杂的同步场景,例如需要手动控制锁的获取与释放、实现公平锁或需要进行锁的中断等情况,使用起来更加灵活。
性能
- synchronized:在Java 6之前性能较差,因为其是重量级锁,会引起线程上下文切换等开销。但Java 6之后,对其进行了优化,引入了偏向锁、轻量级锁等机制,性能得到了很大提升。在竞争不激烈的情况下,性能表现良好。
- ReentrantLock:在竞争激烈的情况下,性能可能优于synchronized。它支持更细粒度的控制,如可以使用非公平锁来提高吞吐量(默认是非公平锁)。并且在高竞争场景下,ReentrantLock的锁获取和释放操作可以通过CAS等无锁机制实现,减少线程上下文切换的开销。
锁的获取与释放机制
- synchronized:当线程进入同步代码块或方法时自动获取锁,当代码块或方法执行完毕(正常结束或抛出异常)时自动释放锁,程序员无法手动控制锁的释放时机。
- ReentrantLock:通过调用
lock()
方法获取锁,需要手动调用unlock()
方法释放锁,通常将unlock()
放在finally
块中以确保锁一定会被释放,防止死锁。例如:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
锁的公平性
- synchronized:是非公平锁,线程竞争锁时,不保证等待时间最长的线程优先获取锁,这种方式在一定程度上可以提高吞吐量。
- ReentrantLock:默认是非公平锁,但可以通过构造函数设置为公平锁。公平锁会按照线程等待的先后顺序分配锁,能保证线程等待的公平性,但公平锁的实现开销较大,会降低系统的吞吐量。例如:
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
优先选择ReentrantLock的情况
- 需要手动控制锁的释放:当需要在特定条件下提前释放锁,或者在不同的代码路径下释放锁时,ReentrantLock更合适,因为它可以手动调用
unlock()
方法。
- 需要实现公平锁:如果业务场景要求线程获取锁的顺序必须是公平的,即等待时间最长的线程优先获取锁,那么应选择ReentrantLock并设置为公平锁。
- 需要锁中断:当线程在获取锁的过程中需要能够响应中断时,ReentrantLock提供了
lockInterruptibly()
方法来实现此功能,而synchronized
关键字无法做到。
- 需要更细粒度的锁控制:例如需要使用读锁、写锁等功能(ReentrantLock可以配合
Condition
实现类似读写锁的功能),或者需要实现锁的超时获取等,ReentrantLock能提供更灵活的控制。