一. 介绍
上篇文章介绍了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接口方法的实现
4.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方法
4.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;
}
4.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唯一的不同就在于如果此时锁空闲,就不会再去管同步队列中是否还有等待线程,而是直接抢锁,也就是我们第三节所说的公平锁与非公平锁的区别
4.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方法合成了一个方法,然后去掉了判断是否被中断过的返回值,加上了抛出异常中断,大家去之前的文章中对比看下即可,这里就不冗述了
4.3 tryLock
tryLock是非阻塞的获取锁,不管成功与失败,都会立即返回结果,我们看下源码
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
我们可以看到,tryLock调用的是Sync抽象类中的nonfairTryAcquire方法,这个方法在前面已经详细的讨论过,这里就不再赘述
可能会有小伙伴有疑问,nonfairTryAcquire这个方法是不管同步队列的情况,直接去抢锁,为什么公平锁的tryLock方法也要调用这个方法。这个问题很好,因为tryLock方法是Lock接口的方法,所以我们在现实时,是需要根据这个接口的语义规范去实现的,所以公平锁要用这个tryLock方法,那就需要实现这个语义
4.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。
4.5 unlock
public void unlock() {
sync.release(1);
}
unlock调用的是Sync的release的,而sync的release是继承了AQS的release方法,这个已经在《AQS—独占锁的释放》中详细阐述过了,这里不再赘述
4.6 newCondition
final ConditionObject newCondition() {
return new ConditionObject();
}
与unlock同理,newCondtition也是继承使用了AQS的newCondititon方法,前文《AQS—ConditionObject》也已经详细阐述
五. 总结
其实通过对ReentrantLock的解读,我们可以看到AQS的重要性,其中许多方法都是继承使用了AQS的方法,所以如果大家想学好java并发编程,则AQS是必须深入了解的。
(完)
欢迎大家关注我的公众号 “程序员进阶之路”,里面记录了一个非科班程序员的成长之路 。