1、概念:ReentrantLock实现了Lock接口,并且提供了和Synchronized相同的互斥性和内存可见性。另外,ReentrantLock并不是用来代替Synchronized的,而是当Synchronized的加锁机制不合适时,作为一种可选择的高级功能。
2、对比Synchronized
- 皆可重入:两者都提供了可重入的语义。
- ReentrantLock更加灵活:
(1)ReentrantLock可以终止一个正在等待锁的线程。
(2)设置轮询锁,尝试获取锁,设置一定次数,轮询该次数后依然没有获取到锁,则放弃获取。
(3)设置定时锁,在规定时间内去获取锁,超过这个时间,则放弃获取。- ReentrantLock必须显示的加锁和释放锁,如果忘记在finally中忘记释放锁,那么就相当于埋下了一颗定时炸弹,因此具有一定的“危险性”;Synchronized在发生异常时会自动释放锁,避免了异常时的死锁。
- 吞吐量:Java5.0时,ReentrantLock性能会好很多;到了Java6.0,两者的效果差不多。
3、公平锁和非公平锁
公平锁确保被阻塞的线程都能够获取到锁,避免了线程获取锁的饥饿现象;但是在激烈竞争的情况下,线程从内核态转变为用户态存在着严重的延迟,此时会带来性能的影响。
非公平锁则是让线程可以”插队“,在非公平锁的情况下,新来的线程只有在锁被别的线程持有时,才会进入队列,不然不会进入队列,而是立即参与竞争,如果竞争成功,则省去了线程状态切换的开销,这说明了某些情况下,非公平锁比公平锁性能要好。但是,非公平锁会出现饥饿现象。
Synchronized是非公平锁。
ReentrantLock可以自主选择,默认是非公平锁;如果创建时传入true,则是公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
4、抉择
只有在内置锁无法满足要求的情况下,才应该去考虑ReentrantLock,ReentrantLock作为一种高级的备选方案,选择它的原因是因为它的高级功能。如若不然,优先考虑Synchronized。
5、原理
- 锁的竞争,通过互斥变量,使用CAS机制实现;没有竞争到锁的线程使用AbstractQueuedSynchronizer线程同步器来存储,同步器底层通过双向链表实现,当锁被释放之后,会从AQS队列头部唤醒一个线程。
- 锁重入原理:AQS里面有一个成员变量来保存当前获取到锁的的线程,如果该线程再次获取锁,那么就可以直接增加锁的重入次数,不会走竞争逻辑。