小伙伴们,大家好啊,这里是经典鸡翅。鸡翅老哥今天想给大家说说多线程的核心 AQS。那么 AQS 是个啥呢?对于可重入锁、自旋锁、locksupport 大家应该是耳熟能详了,但是大家有没有想过他们的底层层面是个什么东西呢。就是今天要说的 AQS,如果你光会使用锁,那么你只是一个 api 使用者,还是要去学习底层知识。今天就和鸡翅老哥一起来看吧!
什么是 AQS ?
AQS,全名 AbstractQueuedSynchronizer ,翻译过来就是说抽象的队列同步器。在jdk8的 java.util.concurrent.locks包下。粗略的看一下长什么样子。
AQS 是构建锁及同步器的核心。内部主要是通过fifo的双向链表队列完成资源的排队,我们看上方图里面有一个volatile的int变量state,这个state就是专门用来标志锁的状态的。
AQS框架概览
AQS重点地方
private volatile int state;
AQS的State变量,0就是没锁,对象可以自由获取;大于等于1,证明锁已经被占用,需要等待释放后,才可以获取。
内部类Node
Node就是我们的每一个等待线程,多个线程获取锁的时候,每个线程就是一个NODE。
static final class Node{
//共享
static final Node SHARED = new Node();
//独占
static final Node EXCLUSIVE = null;
//线程被取消了
static final int CANCELLED = 1;
//后继线程需要唤醒
static final int SIGNAL = -1;
//等待condition唤醒
static final int CONDITION = -2;
//共享式同步状态获取将会无条件地传播下去
static final int PROPAGATE = -3;
// 初始为e,状态是上面的几种
volatile int waitStatus;
// 前置节点
volatile Node prev;
// 后继节点
volatile Node next;
双向链表队列
每一个node的等待状态的先后顺序,通过一个双向链表队列来进行联系。
ReentrantLock为例,了解AQS
ReentrantLock基础
ReentrantLock实现了AbstractQueuedSynchronizer接口,是接口的一个实现类。 ReentrantLock分为公平锁和非公平锁。分别为FairSync 和 NoFairSync,都继承了Sync类。而sync类继承了AbstractQueuedSynchronizer。构建ReentrantLock的时候,传参为true则为公平锁,不传则为false。 接下来我们以ReentrantLock的lock方法来进行展开。
Lock方法的执行
当一个线程第一次执行 lock()方法,state 变量的值等于 0,表示 lock 锁没有被占用,此时执行 compareAndSetState(0, 1)CAS 判断,可得 state == expected == 0,因此 CAS 成功,将 state 的值修改为 1。 另一个线程再次执行lock()方法。state 变量的值等于 1,表示 lock 锁没有被占用,此时执行 compareAndSetState(0, 1) CAS 判断,可得 state != expected,因此 CAS 失败,进入 acquire() 方法。
acquire方法
acquire方法里面第一个执行的tryAcquire方法。
tryacquire方法
以非公平锁为例,tryacquire方法中调用了nonfairTryAcquire方法。
在 nonfairTryAcquire() 方法中:线程 B 执行 int c = getState() 时,获取到 state 变量的值为 1,表示 lock 锁正在被占用;于是执行 if (c == 0) { 发现条件不成立,接着执行下一个判断条件 else if (current == getExclusiveOwnerThread()) {,current 线程为线程 B,而 getExclusiveOwnerThread() 方法返回正在占用 lock 锁的线程,为线程 A,因此 tryAcquire() 方法最后会 return false,表示并没有抢占到 lock 锁。再往下就是addwaiter方法。
addwaiter方法
Node节点用于封装用户线程,这里将当前正在执行的线程通过 Node 封装起来(当前线程正是抢占 lock 锁没有抢占到的线程)
判断 tail 尾指针是否为空,为空,那么执行 enq(node) 方法,将封装了线程 B 的 Node 节点入队。
总结
至此一个线程就已经入队了,交由AQS进行管理。