可重入锁(ReentrantLock),表示该锁能够支持一个线程对资源的重复加锁,还支持两种获取锁的方式:
- 公平锁:按照锁的请求时间顺序获取锁
- 非公平锁:任意一个线程在请求锁时都有机会获得锁
Synchronized与ReentrantLock对比
synchronized
关键字修饰的方法和同步代码块也是支持可重入的,它们的区别是:
Synchronized | ReentrantLock | |
---|---|---|
加锁方式 | 隐式加锁 | 显式加锁,调用lock() 加锁与unlock() 解锁 |
等待锁时是否可中断 | 不可中断 | 可中断 |
锁的公平性 | 非公平锁 | 默认值是非公平锁,可以设置为公平锁 |
锁绑定多个条件 | 只能绑定一个锁条件 | 可以设置多个锁条件Condition 对象 |
ReentrantLock实现可重入分析
tryAcquire()获取锁源码
1 | final boolean nonfairTryAcquire(int acquires) { |
tryRelease()释放锁源码
1 | protected final boolean tryRelease(int releases) { |
可重入分析总结
- ReentrantLock内部维护
state
成员变量,可实现多次对state
变量自增实现可重入锁 - 获取
n
次锁就要释放n
次锁,只有当state
等于0时表示锁成功释放。
公平锁与非公平锁实现分析
ReentrantLock内部有两个内部类FairSync
和NonfairSync
,分别代表公平锁和非公平锁的类,内部重写了AQS
的请求获取锁方法tryAcquire()
方法自定义锁请求方式。先来看lock()
方法
lock()方法分析
1 | // NonfairSync |
从上面的代码可以得到下面的不同点:
- 非公平锁直接
CAS
尝试获取锁,获取失败才调用acquire()
,而公平锁调用acquire()
;
调用acquire()
方法实际上是调用FairSync
和NonfairSync
各自内部的tryAcquire()
方法,
1 | public final void acquire(int arg) { |
tryAcquire()源码解析
NonfairSync的tryAcquire()方法
1 | protected final boolean tryAcquire(int acquires) { |
非公平锁内部调用了nonfairTryAcquire()
方法实现非公平锁的请求,源码如上;非公平锁整个获取锁的流程如下图:
注意:线程被唤醒是所有线程都会被唤醒,而不是只有前驱节点为头节点的线程被唤醒,这是与公平锁的一个重要区别
FairSync的tryAcquire()方法
1 | protected final boolean tryAcquire(int acquires) { |
两个获取锁方法的不同点:
- 如果锁已经被释放(
state = 0
),公平锁先调用方法hasQueuedPredecessors()
判断队列中是否有节点,如果有则排队,而非公平锁则直接CAS
尝试获取锁,源码如下:
1 | // NonfairSync的tryAcquire()方法 |
获取锁失败之后的操作
- 如果获取不到锁,都要调用
acquireQueued()
将线程节点添加到队列尾部,等待执行线程唤醒,addWaiter()
是创建一个等待线程节点,源码如下:
1 | public final void acquire(int arg) { |
ReentrantLock总结
- ReentrantLock内部有公平锁与非公平锁两种实现
- 公平锁按照请求顺序获取锁,非公平锁会同时争抢获取锁
- 非公平锁的性能要高于公平锁,因为请求顺序会带来频繁的线程上下文切换,而非公平锁不需要顾及队列的顺序
- 可重入性是通过
state
同步状态自增实现的