面试必死问题:了解Java的AQS吗?

196 阅读5分钟

乐字节小z带你们了解面试必死问题:了解Java的AQS吗?

AQS简介

AQS是一个抽象类,不可以被实例化,它的设计之初就是为了让子类通过继承来实现多样的功能的。它内部提供了一个FIFO的等待队列,用于多个线程等待一个事件(锁)。它有一个重要的状态标志——state,该属性是一个int值,表示对象的当前状态(如0表示lock,1表示unlock)。AQS提供了三个protected final的方法来改变state的值,分别是:getState、setState(int)、compareAndSetState(int, int)。根据修饰符,它们是不可以被子类重写的,但可以在子类中进行调用,这也就意味着子类可以根据自己的逻辑来决定如何使用state值。 AQS的子类应当被定义为内部类,作为内部的helper对象。事实上,这也是juc种锁的做法,如ReentrantLock,便是通过内部的Sync对象来继承AQS的。AQS中定义了一些未实现的方法(抛出UnsupportedOperationException异常) tryAcquire(int) 尝试获取state tryRelease(int) 尝试释放state tryAcquireShared(int) 共享的方式尝试获取 tryReleaseShared(int) 共享的方式尝试释放 isHeldExclusively() 判断当前是否为独占锁 这些方法是子类需要实现的,可以选择实现其中的一部分。根据实现方式的不同,可以分为两种:独占锁和共享锁。其中JUC中锁的分类为: 独占锁:ReentrantLock、ReentrantReadWriteLock.WriteLock 共享锁:ReentrantReadWriteLock.ReadLock、CountDownLatch、CyclicBarrier、Semaphore 其实现方式为: 独占锁实现的是tryAcquire(int)、tryRelease(int) 共享锁实现的是tryAcquireShared(int)、tryReleaseShared(int)

AQS中还提供了一个内部类ConditionObject,它实现了Condition接口,可以用于await/signal。采用CLH队列的算法,唤醒当前线程的下一个节点对应的线程,而signalAll唤醒所有线程。 总的来说,AQS提供了三个功能: 实现独占锁 实现共享锁 实现Condition模型

AQS通俗举例:

如果看到这里,你还不是特别理解 AQS 的作用,那就请看接下来的这个比喻,我们把 AQS 和线程协作工具类给“拟人化”,比作是 HR 和面试官。这里模拟候选人参加校招面试的场景。对公司而言,面试一般需要面试官和 HR 参加。通常有两种面试,一种是群面,一种是单面,群面是指多个同学一起参加的面试,例如规定是 10 个人一起面试,那群面规则就是先凑齐 10 个人,再统一面试。 而单面往往是流水线形式的、一对一的面试。假设我们一共有 5 个面试官进行单面,即这 5 个面试官同时分别面试一个候选人,在面试过程中,候选人会进行排队,前面的候选人面试完了以后,后面候选人就跟上,找空闲的面试官开始面试,这就是单面的场景。乍看起来,群面和单面的面试规则是很不一样的:前者是多人一起面试,而后者是逐个面试。但也其实,群面和单面也有很多相同的地方(或者称为流程或环节),而这些相同的地方往往都是由 HR 负责的。比如面试者来了,HR 需要安排候选人签到、就坐等待、排队,然后 HR 要按顺序叫号,从而避免发生多个候选人冲突的情况,同时 HR 还要确保等待的同学最终都会被叫到,这一系列的内容都由 HR 负责,而这些内容无论是单面还是群面都是一样的。这些 HR 在面试中所做的工作,其实就可以比作是 AQS 所干的活儿。至于具体的面试规则,比如群面规则是 5 个人还是 10 个人一起?是单面还是群?这些是由面试官来安排的。对于面试官而言,他不会关心候选人是否号码冲突、如何等待、如何叫号,是否有休息的场地等,因为这是 HR 的职责范围。这里的面试官就对应利用了 AQS 实现具体的协作逻辑的工具类,而 HR 则代表 AQS。刚才所说的让候选人休息,就是指把线程进行阻塞,不要持续耗费 CPU;而后续叫号让候选人去面试,则意味着去唤醒线程。群面的流程类似于 CountDownLatch,CountDownLatch 会先设置需要倒数的初始值,假设为 10,每来一个候选人,计数减 1,如果 10 个人都到齐了,就开始面试。同样,单面可以理解为是 Semaphore 信号量,假设有 5 个许可证,每个线程每次获取 1 个许可证,这就类似于有 5 个面试官并行面试,候选人在面试之前需要先获得许可证,面试结束后归还许可证。对于 CountDownLatch 和 Semaphore 等工具类而言,它要确定自己的“要人”规则,是凑齐 10 个候选人一起面试,像群面一样呢?还是出 1 进 1,像单面一样呢?确定了规则之后,剩下的类似招呼面试者(类比于调度线程)等一系列工作可以交给 AQS 来做,这样一来,各自的职责就非常独立且分明了。

总结:

⁣学习AQS 的思路,为什么需要 AQS,以及 AQS 的作用,利用 AQS 可以很方便的实现线程协作工具类,而且 AQS 被广泛应用在了 JUC 包中。

文章转自:乐字节