AQS

1、简介

AQS 的全称为(AbstractQueuedSynchronizer),是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器, 比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于AQS 的。

2、原理

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒 时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。


AQS_1.png

2.1 AQS 定义两种资源共享方式

2.1.1 Exclusive(独占)

只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。
当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态

2.1.2 Share(共享)

多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWtireLock。

3、源码解读

3.1 acquire

acquire.png

3.2 tryAcquire(非公平)

tryAcquire1.png

判断state是否等于0,如果等于0,利用CAS原子方式设置state为1,并且设置持有锁的线程为当前线程,加锁成功。
如果不等于0,判断当前线程是否为锁持有线程,如果是,重入+1,加锁成功。如果不是,加锁失败。

3.3 tryAcquire(公平)

tryAcquire2.png

判断state是否等于0,如果等于0,判断AQS等待队列中是否有元素存在。
如果不存在其他等待线程,利用CAS原子方式设置state为1,并且设置持有锁的线程为当前线程,加锁成功。
如果存在其他等待线程,那么自己也会加入到等待队列尾部,加锁失败。
如果不等于0,判断当前线程是否为锁持有线程,如果是,重入+1,加锁成功。如果不是,加锁失败。

3.4 addWaiter

addWaiter.png

创建一个和当前线程绑定的Node节点,Node为双向链表。如果tail节点为空,直接调用enq(node)方法,创建Node节点作为head节点,并将当前node加入尾部。
如果tail节点不为空,将当前线node节点程加入等待队列尾部。

3.5 end

end.png

第一遍循环时tail指针为空,进入if逻辑,使用CAS操作设置head指针,将head指向一个新创建的Node节点。执行完成之后,head、tail、t都指向第一个Node元素。
接着执行第二遍循环,进入else逻辑,此时已经有了head节点,这里要操作的就是将线程对应的Node节点挂到head节点后面。

3.6 acquireQueued

acquireQueued.png

先判断当前传入的Node对应的前置节点是否为head,如果是则尝试加锁。加锁成功过则将当前节点设置为head节点,然后空置之前的head节点,方便后续被垃圾回收掉。
如果加锁失败或者Node的前置节点不是head节点,就会通过shouldParkAfterFailedAcquire方法。

3.7 shouldParkAfterFailedAcquire

shouldParkAfterFailedAcquire.png

3.8 parkAndCheckInterrupt

parkAndCheckInterrupt.png

将head节点的waitStatus变为了SIGNAL=-1,最后执行parkAndChecknIterrupt方法,调用LockSupport.park()挂起当前线程。

3.9 release

release.png

3.10 tryRelease

tryRelease.png

设置state=0,持有锁的线程为null。

3.11 unparkSuccessor

unparkSuccessor.png

判断head节点waitStatus是否小于0,如果小于0,利用CAS方式设置值为0。判断head节点的下一个节点是否为null,如果不为null,调用调用LockSupport.unpark()唤醒head节点的下一个节点对应的线程。

3.12 唤醒之后acquireQueued

acquireQueued.png

此时线程被唤醒,继续执行for循环,判断线程的前置节点是否为head,如果是则继续使用tryAcquire()方法来尝试获取锁,其实就是使用CAS操作来修改state值,如果修改成功则代表获取锁成功。接着将当前线程设置为head节点,然后空置之前的head节点数据,被空置的节点数据等着被垃圾回收。如果修改失败,调用LockSupport.park()挂起当前线程。

3.13 cancelAcquire

cancelAcquire.png

acquireQueued过程中,tryAcquire加锁异常(未获取成功),或者interrupted异常,会进入cancelAcquire。
将node节点状态改成CANCELLED=1,节点对应的线程置空。
node.thread = null;
node.waitStatus = Node.CANCELLED;
将node节点前面非取消节点、后面非取消节点连接起来。
如果前面非取消节点释放了锁,当前节点唤醒后续节点。

//waitStatus = 1 取消状态  -1 待唤醒状态  -2 条件等待状态
//从node节点开始往前找,找到第一个状态不是取消的节点pred
Node pred = node.prev;
while (pred.waitStatus > 0)
    node.prev = pred = pred.prev;


if (node == tail && compareAndSetTail(node, pred)) {
        //如果当前节点是尾节点,设置pred为尾节点,设置predNext为null,这样predNext-node节点被回收
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                //从node节点往后找,找到第一个非取消节点next,将pred的next节点指向next
                compareAndSetNext(pred, predNext, next);
        } else {
            //进入cancelAcquire的线程中有tryAcquire加锁异常(未获取成功)的线程,需要调用unparkSuccessor唤醒当前节点next节点对应的线程
            unparkSuccessor(node);
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容