面试题答案
一键面试功能特性差异
- 锁的获取方式
- synchronized:是隐式锁,由JVM自动获取和释放。当线程进入同步代码块或方法时自动获取锁,退出时自动释放。
- ReentrantLock:是显式锁,需要手动调用
lock()
方法获取锁,调用unlock()
方法释放锁,且必须在finally
块中释放锁以确保锁一定会被释放,避免死锁。
- 可中断性
- synchronized:不可中断,一旦线程获取到锁进入同步块,除非正常执行完毕或者抛出异常,否则其他线程无法中断它。
- ReentrantLock:可以通过
lockInterruptibly()
方法在等待锁的过程中响应中断。
- 公平性
- synchronized:是非公平锁,线程获取锁的顺序是随机的,在高并发情况下可能导致某些线程长时间等待(线程饥饿)。
- ReentrantLock:默认是非公平锁,但可以通过构造函数创建公平锁,公平锁会按照请求锁的顺序来分配锁,减少线程饥饿现象,但公平锁的性能一般低于非公平锁。
- 锁的绑定多个条件
- synchronized:一个锁只能与一个
Object
的wait - notify
机制关联。 - ReentrantLock:可以通过
newCondition()
方法创建多个Condition
对象,每个Condition
对象都可以实现类似wait - notify
的功能,适用于更复杂的线程间通信场景。
- synchronized:一个锁只能与一个
性能表现差异
- JDK早期版本 在低并发情况下,synchronized和ReentrantLock性能相近。但在高并发情况下,synchronized由于采用重量级锁,性能较差,而ReentrantLock使用乐观锁机制和CAS操作,性能更优。
- JDK 1.6及以后 synchronized进行了大量优化,引入偏向锁、轻量级锁等机制,性能有了很大提升。在竞争不激烈的情况下,synchronized性能甚至优于ReentrantLock;在竞争激烈时,ReentrantLock性能仍可能更好,特别是需要可中断锁、公平锁等特性时。
使用场景差异
- 高并发读写场景
- 优先选择ReentrantLock的情况:
- 当读操作和写操作的线程都需要可中断性时,例如在某些情况下需要取消正在等待锁的线程操作,ReentrantLock可以通过
lockInterruptibly()
方法满足需求,而synchronized无法实现。 - 当需要公平锁来保证所有线程都有机会获取锁,减少线程饥饿现象时,ReentrantLock可以通过构造函数创建公平锁,而synchronized是非公平锁。
- 当需要更灵活的线程间通信,例如使用多个
Condition
来实现不同条件下的等待和唤醒时,ReentrantLock更合适。
- 当读操作和写操作的线程都需要可中断性时,例如在某些情况下需要取消正在等待锁的线程操作,ReentrantLock可以通过
- 优先选择synchronized的情况:
- 当读写操作竞争不激烈,代码追求简洁性时,synchronized作为隐式锁,代码书写更简洁,无需手动管理锁的获取和释放,JVM的优化也能保证较好的性能。
- 当需要与其他使用synchronized的代码进行兼容,避免混合使用不同的锁机制导致复杂的问题时,应选择synchronized。
- 优先选择ReentrantLock的情况: