1、重入锁ReentrantLock简介
- 该锁支持一个线程对资源的重复加锁,注意不是支持多个线程同时获取资源
- 获取锁时,支持公平性 和 非公平性
- synchronized 隐式的支持重进入
- 注意,这是一个独占式的获取
2、公平性与非公平性的比较
-
公平锁的效率没有 非公平高
-
公平锁能够减少饥饿
-
默认是非公平,可以从源码中看出来
-
非公平锁:不能保证正在排队的线程能拿到锁,因为可能被新来的线程抢走
3、继承关系图
4、实现重进入
- 线程再次获取锁:需要判断当前要获取锁的线程是否为当前占有锁的线程
- 锁的最终释放:线程n次获取锁,则需要释放n次,其他线程才能再获取锁
5、源码分析
ReentrantLock是在AQS的基础上实现的。其内部有3个内部类:Sync、NonfairSync 、 FairSync
5.1 、Sync
Sync继承AQS,是我们自定义的同步器,NonfairSync 、 FairSync都是在继承Sync的基础上发展来的。Sync中的方法默认是非公平的。Sync主要实现了方法 tnonfairTryAcquire(int) tryRelease(int) 等。注意公平、非公平只是获取不同,释放都是一样的。定义了抽象方法lock(int),没有定义unlock(int)方法,这个方法在ReentrantLock()中定义。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock(); //具体实现在其子类中
//重点1(获取资源)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //当前线程
int c = getState(); //获取当前剩余资源数
if (c == 0) { //当前没有线程占有资源
//尝试CAS设置status,因为在if(c==0)到CAS设置之间可能有其他线程插队,因此有失败的可能性
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //如果当前线程是占有线程
int nextc = c + acquires; //先计算出预期的status,注意此时并没有设置status
if (nextc < 0) // 参数异常
throw new Error("Maximum lock count exceeded");
setState(nextc); //设置status,不用考虑多西安城竞争(以为只有本线程才可以到这里)
return true;
}
return false;
}
//重点2(释放资源,不一定是完全释放)
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //计算预期status
//如果当前线程并不是占有线程,则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false; //完全释放标志
if (c == 0) { //status==0,表示完全释放
free = true;
setExclusiveOwnerThread(null); //设置占有线程为空
}
setState(c); //设置status(不用考虑多线程竞争,因为只有持有资源的线程1个,才可以到这里)
return free;
}
//是否被当前线程占有
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取当前占有资源的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取占有次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
我们重点应该关注在 获取资源 | 释放资源的两个方法,特点如下:
- Sync实现的是 非公平(NonfairSync)的方法。因此nonfair中只tryAcquire(int)只是简单的调用Sync中的放啊;而fair中却要自己另外实现tryAcquire(int)方法
- lock() 在Sync中式抽象方法,具体实现在其子类中
- 释放资源时,先判断当前线程是否是占有线程。如果不是,则抛出异常;如果是,则在设置status时,不同考虑多线程竞争问题,因为支持持有资源的线程才可以设置status
- 在获取资源时,要先判statuc==0?。如果当前没有线程占有,则要CAS设置status,因为多线程竞争。如果有线程占有,要分下面两种情况:时当前线程占有,则直接设置status,不用CAS;如果不是当前线程占有,抛出异常
- 在初次占有时,和完全释放时,注意要设置占有线程
5.2、 NonfairSync
NonfairSync继承自Sync,并且Sync默认是实现的nonfair,因此tryAcquire(int)只是简单的调用了nonfairTryAcquire(int),而tryRelease(int)则是从Sync中继承来的。lock()需要自己实现。没有unlock(int)方法。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//获取锁
final void lock() {
if (compareAndSetState(0, 1)) //先尝试CAS设置0-1,如果设置成功,则设置占有线程
setExclusiveOwnerThread(Thread.currentThread());
else //CAS设置0-1失败,执行Sync中的acquire(1),而Sync中acquire又是调用tryAcquire(int)方法
acquire(1);
}
//获取资源只是简单的调用Sync中的nonfairTryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
可以看出NonFairSync中的lock(int)方法就是调用Sync(AQS)中的acquire(int)方法,而acquire(int)方法调用tryAcquire(int)方法,tryAcquire(int)调用 nonfairTryAcquire(int) 方法。
5.3、FairSync
FairSync中的tryAcquire(int)方法是自己实现的,和Sync中的 nonfairTryAcquire(int) 无关。lock(int) 放啊也是直接调用的自己(从AQS继承传递)的 acquire(int) 方法。没有unlock(int) 方法。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//获取锁,直接调用AQS继承来的acquire(int)方法,acquire(int)中调用tryAcquire(int)
final void lock() {
acquire(1);
}
//实现tryAcquire(int)获取共享资源
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //当前线程
int c = getState(); //当前status
if (c == 0) { //如果还没有线程占有
//注意,这里是体现fair的地方(先判断当前节点有没有前驱节点),排队越久,越优先
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { //CAS设置status,因为此处有竞争
setExclusiveOwnerThread(current); //如果CAS成功,则设置线程(注意,如果能到这里,肯定只有CAS成功的线程一个,不存在竞争)
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //当前线程是占有线
int nextc = c + acquires; //计算预期status
if (nextc < 0) //参数异常
throw new Error("Maximum lock count exceeded");
setState(nextc); //设置status,这里不存在竞争
return true;
}
return false;
}
}
//判断当前线程有没有前驱节点,有则返回true
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
总结:
- 不同于NonFair的tryAcquire(int)直接调用Sync的nonFairTryAcquire(int)。FairSync需要自己写tryAcquire(int)方法。与NonFair不同的地方是,加入了 hasQueuedPredecessors() 判断当前线程有没有前驱节点。如果没有前驱节点,那么它可以去竞争资源。
- lock(int)方法和非公平一样,只是简单的调用 acquire(int)
5.4、ReentrantLock中的成员变量和构造方法
private static final long serialVersionUID = 7373984872572414699L; //序列化
private final Sync sync; //内部的同步器,这只是个引用,具体实例肯定是 FairSync或者NonFairSync
//构造方法
//1.默认,非公平
public ReentrantLock() {
sync = new NonfairSync();
}
//2.根据参数boolean来构造内部同步器类型
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
总结:
- ReentrantLock的成员变量非常简单,就是有一个序列化参数 和 Sync自定义同步器
- ReentrantLock的公平性实现方式:Sync的具体实现,根据构造方法的参数来实例化 FairSync或者NonFairSync实例
5.5、ReentrantLock的各种获取锁(正常、响应中断、超时等)
public void lock() { //不响应中断
sync.lock(); //就是简单的调用Sync子类的lock方法,实质上是调用AQS的acquire(int)
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1); //调用AQS的acquireInterruptibly(1)
}
//注意acquireInterruptibly(1)中也是调用了tryAcquire(int)方法,而tryAcquire(int)方法在公平锁和非公平锁的实现不同。
注意:lockInterruptibly()和其他所有的lock()方法最后在底层都调用了tryAcquire()。因此任何lock方法都区分公平 与 非公平。
//非阻塞的获取锁(非公平)
public boolean tryLock() {
return sync.nonfairTryAcquire(1); //少了acquire中的acquireQueud()阻塞
}
//超时获取锁(响应中断)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
总结:除了tryLock(int)是直接调用的Sync.nonfairTryAcquire(1),其他的获取锁都是调用的AQS中对应的方法
5.6、释放锁
public void unlock() {
sync.release(1); //不管公不公平,都是调用Sync中的release(int),也是Sync直接从AQS中继承来的
}
总结:直接调用的AQS中的release(1),Sync并没有改写release()
5.7、ReentrantLock中的其他方法(不一一列举,之说自己感兴趣的)
//判断公平性
public final boolean isFair() {
return sync instanceof FairSync;
}
6、总结
由上面的分析,可以得到下面的结论
比较底层的同步队列进队、出队问题,已经在AQS解决了。而实现ReentrantLock时,主要时实现其内部自定义同步器Sync。但ReentrantLock分为两种模式:公平、非公平;这两种模式的实现是在 FairSync 和 NonFairSync 继承Sync的基础上,对 tryAcquire(int)和lock(int)的具体实现不同来实现的。而 unlock() 不区分公平性。