菜鸡每日一面系列打卡21天
每天一道面试题目
助力小伙伴轻松拿offer
坚持就是胜利,我们一起努力!
题目描述
谈谈你对AQS的理解。
题目分析
在之前的文章中我们提到过,ReentrantLock等一系列JDK所提供的同步器都是基于AQS构建的,因此可以说,不了解AQS,就不算真正掌握了同步器。而有关AQS的底层原理的考查,也是各个大厂面试过程中的热点问题。
本文将从源码级别深入分析AQS,并以ReentrantLock为例,介绍如何使用AQS构建同步器。不了解ReentrantLock的小伙伴,可以先移步文末的相关链接进行学习。菜鸡希望,本篇文章能使小伙伴们彻底搞懂AQS以及模板方法设计模式,从而可以随心所欲地构建自定义同步器,那么,面试自然就不在话下了。
接下来,随菜鸡一起去看看吧。
题目解答
01
什么是AQS
AQS是AbstractQueuedSynchronizer类的简称,从名字我们可以猜测,AQS是一个虚拟类,基于队列实现,用于构建同步器。这个猜测是正确的,它是JDK提供的一个基于FIFO等待队列实现阻塞锁及相关同步器(Semaphore等)的一个框架,它是大多数同步器的基础,用一个volatile的int类型变量state来表示状态。
AQS的核心设计思想是,如果当前线程请求的共享资源处于闲置状态,则将该线程设置为工作线程,并将该资源设置为锁定状态。如果该资源处于锁定状态,则将该线程加入CLH队列中。
那么问题来了!什么是CLH队列?无需解释,看完下图就全明白了。
02
源码分析
上文谈到了AQS是用一个volatile的int类型变量state来表示状态。接下来我们看一下源码。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
......
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
......
}
AQS定义了两种资源共享方式,独占锁与共享锁。所谓的独占锁就是,只有一个线程可以执行,比如之前谈过的ReentrantLock就是独占锁的一种,而共享锁,就是多个线程可以执行,比如上文提到的Semaphore。在此就不展开。
以ReentrantLock为例,我们可以学习基于AQS的公平锁与非公平锁的实现。我们看一下ReentrantLock类中相关部分的源码,首先,我们看一下它的构造方法。
public class ReentrantLock implements Lock, java.io.Serializable {
......
private final Sync sync;
......
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
......
}
如源码所示,ReentrantLock的默认构造函数是非公平锁。接下来,我们看一下NonfairSync类与FairSync类的不同之处。首先,我们看一下公平锁的实现。
public class ReentrantLock implements Lock, java.io.Serializable {
......
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
}
可以看出,非公平锁和公平锁主要有以下不同:
二者的lock方法不同,非公平锁在调用lock方法后,会CAS抢占锁,如果锁没有被占用,则直接获取。
非公平锁在CAS失败后,会同公平锁一样进入到tryAcquire方法,如果发现锁被释放(state == 0),非公平锁会CAS抢占锁,而公平锁则会检查等待队列是否有处于等待状态的线程。
03
模板方法模式
同步器的设计是基于模板方法模式的,从ReentrantLock的源码我们也可以看出,自定义同步器方式大致如下:
使用者继承AQS并重写指定的方法。
将AQS以组合的形式引入自定义同步组件的实现中,并调用其模板方法,模板方法会调用重写的方法。
具体需要重写的方法可以参考ReentrantLock的源码,在此便不再赘述。
以上便是菜鸡对AQS的一些总结,供大家参考。
相关链接
学习 | 工作 | 分享
????长按关注“有理想的菜鸡”
只有你想不到,没有你学不到