暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ReentrantLock 源码解读

24只羊羊羊 2021-03-29
273


上篇文章介绍了Lock接口,这篇文章我们就来看看实现Lock接口的ReentrantLock类



一.  属性



ReentrantLock只有两个属性

private static final long serialVersionUID = 7373984872572414699L;
// 提供了所有实现机制
private final Sync sync;

Sync是ReentrantLock的一个内部抽象类,它继承了AQS,而ReentrantLock另外两个内部类:FairSync和NonFairSync则同时继承了Sync,并实现了Sync的lock方法。

总结来说,这三个内部类共同实现了AQS的所有钩子方法,并实现了一个Lock方法,而后面我们会看到,ReentrantLock类中实现的Lock接口的方法都是通过调用属性sync中的方法来实现的




二. 构造方法



ReentrantLock有两个构造方法

  public ReentrantLock() {
sync = new NonfairSync();
}


public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

无参构造方法是创建了非公平锁,而我们也可以通过传入boolean变量来指定创建公平锁。非公平锁和公平锁有什么区别呢?

当一个线程在请求非公平锁的时候,如果锁此时被释放出来,则这个线程无需排队就可以直接获得锁,简称插队获取锁。那这样有啥好处呢?

提高锁资源的利用率,因为唤醒挂起的线程到该线程获得锁是需要时间的,这段时间锁是处于空闲状态,如果我们此时请求公平锁的线程能直接获得这把锁,就把这个浪费的时间利用起来了,虽然对已经排队的线程不太公平,所以也叫非公平锁



三. Lock接口方法实现



3.1 Lock

公平锁实现

final void lock() {
acquire(1);
}

非公平锁的实现

final void lock() {
// 如果cas操作能够将state置为1,则说明此时锁空闲,如果抢锁成功,则将独占线程设置为自己;如果锁此时不空闲,则调用acquire方法
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

非公平锁在调用lock方法后会直接通过cas操作抢锁,如果成功则将独占线程置为自己,否则就调用AQS的acquire方法;而公平锁则直接调用acquire方法

我们在《AQS(1)—并发三板斧》中说过,AQS已经将acquire的大部分逻辑实现了,只留下了一个tryAcquire方法让子类重写,我们现在就来看下公平锁和非公平锁实现的tryAcquire方法

3.1.1 FairSync实现的tryAcquire

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取锁的状态
int c = getState();
// 为0说明锁空闲
if (c == 0) {
// 通过hasQueuePredecessors方法判断同步队列是否为空,如果为false则说明为空,则我们再通过cas去获取锁,如果也成功,则将当前线程设置为独占线程,并返回true
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果c>0则锁已经被使用
// 如果此时占用锁的线程就是本线程,说明本线程已经拿到锁(可重入锁)
// 最后更新state返回true
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0 if (nexextct< 0 <)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 拿锁不成功返回false
return false;
}

3.1.2 NonfairSync实现的tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 与公平锁tryAcquire唯一的不同就是在这没有判断同步队列是否还有等待线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

非公平锁tryAcquire方法是调用的Sync方法的nonfairTryAcquire,而nonfairTryAcquire与公平锁的tryAcquire唯一的不同就在于如果此时锁空闲,就不会再去管同步队列中是否还有等待线程,而是直接抢锁,也就是我们第三节所说的公平锁与非公平锁的区别


3.2 LockInterruptibly

LockInterruptibly和lock方法一样,也是阻塞获取锁,但不同于lock方法,它会响应中断

public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

可以看到,LockInterruptibly调用的sync的acquireInterruptibly,接下继续看acquireInterruptibly方法

public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 查看标志位判断是否被中断
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}

可以看到,进入方法,我们首先会判断此线程是否被中断过,如果被中断,则立马抛出中断异常,如果没有,我们则会通过上小节讨论过的tryAcquire方法去获取锁,如果获取失败,我们则调用doAcquireInterruptibly方法

private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

是不是很眼熟?对,这个方法其实就是将AQS中addWaiter方法和acquireQueued方法合成了一个方法,然后去掉了判断是否被中断过的返回值,加上了抛出异常中断,大家去之前的文章中对比看下即可,这里就不冗述了


3.3 tryLock

tryLock是非阻塞的获取锁,不管成功与失败,都会立即返回结果,我们看下源码

public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}

我们可以看到,tryLock调用的是Sync抽象类中的nonfairTryAcquire方法,这个方法在前面已经详细的讨论过,这里就不再赘述

可能会有小伙伴有疑问,nonfairTryAcquire这个方法是不管同步队列的情况,直接去抢锁,为什么公平锁的tryLock方法也要调用这个方法。这个问题很好,因为tryLock方法是Lock接口的方法,所以我们在现实时,是需要根据这个接口的语义规范去实现的,所以公平锁要用这个tryLock方法,那就需要实现这个语义

3.4 tryLock(long timeout, TimeUnit unit)

tryLock是立即返回结果,而tryLock(long timeout, TimeUnit unit)是带有超时时间的,则可能会阻塞timeout才返回结果

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}

可以看到,tryLock(long timeout, TimeUnit unit)调用的是Sync的tryAcquireNanos方法,而此方法首先会判断线程是否被中断,如果被中断过,则会抛出中断异常,否则就会调用tryAcquire或者doAcquireNanos方法来获取锁

tryAcquire在上面已经说过了,我们来看下doAcquireNanos方法,其实doAcquireNanos方法也是由AQS实现的


我们可以看到,doAcquireNanos方法和上面说的doAcquireInterruptibly很像,只是多了对时间的检查(红色框部分),且有返回值,如果是获取到了锁就返回true,而超时了就返回false。

3.5 unlock
public void unlock() {
sync.release(1);
}

unlock调用的是Sync的release的,而sync的release是继承了AQS的release方法,这个已经在《AQS—独占锁的释放》中详细阐述过了,这里不再赘述

3.6 newCondition
final ConditionObject newCondition() {
return new ConditionObject();
}

与unlock同理,newCondtition也是继承使用了AQS的newCondititon方法,前文《AQS—ConditionObject》也已经详细阐述


四. 总结



其实通过对ReentrantLock的解读,我们可以看到AQS的重要性,其中许多方法都是继承使用了AQS的方法,所以如果大家想学好java并发编程,则AQS是必须深入了解的。

(完)

文章转载自24只羊羊羊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论