浅谈AQS如何入队|八月更文挑战

720 阅读3分钟

小伙伴们,大家好啊,这里是经典鸡翅。鸡翅老哥今天想给大家说说多线程的核心 AQS。那么 AQS 是个啥呢?对于可重入锁、自旋锁、locksupport 大家应该是耳熟能详了,但是大家有没有想过他们的底层层面是个什么东西呢。就是今天要说的 AQS,如果你光会使用锁,那么你只是一个 api 使用者,还是要去学习底层知识。今天就和鸡翅老哥一起来看吧!

什么是 AQS ?

AQS,全名 AbstractQueuedSynchronizer ,翻译过来就是说抽象的队列同步器。在jdk8的 java.util.concurrent.locks包下。粗略的看一下长什么样子。

image.png

AQS 是构建锁及同步器的核心。内部主要是通过fifo的双向链表队列完成资源的排队,我们看上方图里面有一个volatile的int变量state,这个state就是专门用来标志锁的状态的。

AQS框架概览

image.png

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的等待状态的先后顺序,通过一个双向链表队列来进行联系。

image.png

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方法

image.png 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 节点入队。

image.png

总结

至此一个线程就已经入队了,交由AQS进行管理。