这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

如果你想深入研究Java并发的话,那么AQS一定是绕不开的一块知识点,Java并发包很多的同步工具类底层都是基于AQS来实现的,比如我们工作中经常用的Lock工具ReentrantLock、栅栏CountDownLatch、信号量Semaphore等,而且关于AQS的知识点也是面试中经常考察的内容,所以,无论是为了更好的使用还是为了应付面试,深入学习AQS都很有必要。

CAS

学习AQS之前,我们有必要了解一个知识点,就是AQS底层中大量使用的CAS,关于CAS,大家应该都不陌生,如果还有哪位同学不清楚的话,可以看看我之前的文章《面试必备知识点:悲观锁和乐观锁的那些事儿》 ,这里不多复述,哈哈,给自己旧文章加了阅读量

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

此时,好几块搬砖朝我飞了过来。。。。。

好吧,开个玩笑,还是大概讲解一下吧,了解的同学可以跳过这一段。

CAS是乐观锁的一种思想,它假设线程对资源的访问是没有冲突的,同时所有的线程执行都不需要等待,可以持续执行。 如果有冲突的话,就用比较+交换的方式来检测冲突,有冲突就不断重试。

CAS的全称是Compare-and-Swap,也就是比较并交换,它包含了三个参数:V,A,B,V表示要读写的内存位置,A表示旧的预期值,B表示新值,当执行CAS时,只有当V的值等于预期值A时,才会把V的值改为B,这样的方式可以让多个线程同时去修改,但也会因为线程操作失败而不断重试,对CPU有一定程序上的开销。

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

AQS简介

本文主角正式登场。

AQS,全名AbstractQueuedSynchronizer,是一个抽象类的队列式同步器,它的内部通过维护一个状态volatile int state(共享资源),一个FIFO线程等待队列来实现同步功能。

state用关键字volatile修饰,代表着该共享资源的状态一更改就能被所有线程可见,而AQS的加锁方式本质上就是多个线程在竞争state,当state为0时代表线程可以竞争锁,不为0时代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,这些线程会被UNSAFE.park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。

而这个等待队列其实就相当于一个CLH队列,用一张原理图来表示大致如下:

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

基础定义

AQS支持两种资源分享的方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

自定义的同步器继承AQS后,只需要实现共享资源state的获取和释放方式即可,其他如线程队列的维护(如获取资源失败入队/唤醒出队等)等操作,AQS在顶层已经实现了,

AQS代码内部提供了一系列操作锁和线程队列的方法,主要操作锁的方法包含以下几个:

compareAndSetState():利用CAS的操作来设置state的值

tryAcquire(int):独占方式获取锁。成功则返回true,失败则返回false。

tryRelease(int):独占方式释放锁。成功则返回true,失败则返回false。

tryAcquireShared(int):共享方式释放锁。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

tryReleaseShared(int):共享方式释放锁。如果释放后允许唤醒后续等待结点返回true,否则返回false。

像ReentrantLock就是实现了自定义的tryAcquire-tryRelease,从而操作state的值来实现同步效果。

除此之外,AQS内部还定义了一个静态类Node,表示CLH队列的每一个结点,该结点的作用是对每一个等待获取资源做了封装,包含了需要同步的线程本身、线程等待状态.....

我们可以看下该类的一些重点变量:

static final class Node { /** 表示共享模式下等待的Node */ static final Node SHARED = new Node(); /** 表示独占模式下等待的mode */ static final Node EXCLUSIVE = null; /** 下面几个为waitStatus的具体值 */ static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; /** 表示前面的结点 */ volatile Node prev; /** 表示后面的结点 */ volatile Node next; /**当前结点装载的线程,初始化时被创建,使用后会置空*/ volatile Thread thread; /**链接到下一个节点的等待条件,用到Condition的时候会使用到*/ Node nextWaiter; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjfxf.html