在初次学习AQS的时候,大部分人的解释大概都是这样的:
AQS(AbstractQueuedSynchronizer),中文直译过来就是抽象队列同步器,它内部维护一个用volatile关键字修饰的state变量,和一个FIFO双向队列,其中state表示同步状态,即>0表示锁已被抢占,反之表示锁可被抢占(基于最经典的ReentrantLock得出的片面结论,你可以自己去定义state的值的含义)而队列则是来完成资源获取线程的排队工作。
AQS是一个使用模版模式来定义的抽象类,其中有5个指定的方法,3个来访问或者修改state的方法,和9个模版方法以及5个重写过的方法。
而模版模式的主要原理就是,我觉得某一部分需要实现者来定义具体的运行过程,最后我再来调用实现者的代码,从而达到定制化的运行效果。
所以很多人会说AQS是同步基础,这句话说得很模糊,但如果我说它是实现一个锁的基础,那就很好理解了。
在理解AQS之前,我们可以先来看看synchronize的底层原理,但是我这里并不会讲它有什么作用,怎么用,不会可以问ai。
1.率先了解synchronized
我想说说java是如何实现synchronized:
synchronized底层使用monitor来实现,而monitor可以看作是一个工具,其内部是这样的:

其中最应该注意的是三个变量:owner,waitSet和EntryList,owner表示拥有该monitor的线程,如果没有,则表示锁可被抢占。而waitSet就是大家常说的等待池,EntrySet就是传说中的锁池。意思就是当线程没有抢到锁就会进入EntrySet锁池,而被调用wait()方法放弃锁而等待的时候会进入等待池。
这里重点说一下:这也是为什么wait()和notify()方法为什么会被放在Object类中而不是Thread类中,因为synchronized更多的时候与对象锁息息相关,放到Object里面就可以很轻易地分清楚,你在哪个对象锁的等待池,或者你要唤醒哪个对象等待池里的线程。
当然了,这也是为什么wait()方法必须要配合锁来使用,因为调用了这个方法之后,线程会释放锁去等待,你都没有锁的话,你释放什么?
2.AQS到底是什么?
说到这里,AQS的东西应该可以讲得很清楚了,AQS中的FIFO同步队列其实对应了synchronized中的锁池,意思就是你的线程没抢到锁之后,会被放入它维护的FIFO队列中,它的底层使用cas去设置state的状态,并且封装了硬件层面上加锁解锁的操作。
AQS是给其他人去实现一个完整的锁的其中一部分,而这个部分等同于锁池+加锁解锁的操作。所以AQS是面向锁的实现者的。

3.Lock与Condition
说完了AQS,再来说说Lock与condition。
Lock其实只是一个接口,它定义了加锁和解锁的抽象方法,但是并没有去实现,但如果你翻看过Lock的源码,你一定见过它把Condition类写在了内部,而且在它的实现类conditionObject,condition也维护了一个拥有头指针和尾指针的队列。
很多人说Condition叫做有条件等待队列,这句话非常地抽象,但如果我说它本质上跟sychronized中的等待池性质差不多时,你可能也就恍然大悟了。
这也就是为什么,需要先创建lock你才能创建Condition了。Condition中提供了类似于wait()和notify()一样的方法,就是await()和signal()。当你调用await()时,线程的状态也跟wait()差不多,放弃锁并且等待其他线程唤醒,这也就是为什么放到Lock中的原因。
4.Reentantlock作为锁实现的模版
说完了上面的三样“工具”,Reentantlock为想要自己去实现锁逻辑的开发者提供了一些思路,但是阅读源码的时候不难发现,Reentantlock继承了lock接口之后,在加锁和解锁代码中直接调用实现AQS的sync类中的实现代码。其实去深究为什么这么做,我个人觉得没有意义,因为感觉原因很简单,java语言中对规范的要求是比较显式的,AQS帮实现者们封装了底层实现,所以我们就可以在Lock接口的规范下,实现一个Lock锁了,重点就是区分synchronized锁。
说到这里,希望读者们对AQS,Lock 以及Condition能有一个大概的理解思路了,总体来说,它把实现一个完整锁的重要部分拆解成大家熟知的AQS,lock 和Condition,以便于实现者能够最大自由度地去实现定制化式的锁结构。
而对于AQS为什么会被称为同步机制的基础,我想原因已经找到了,就是它封装了底层对于加锁解锁的功能,使实现者能专注于锁结构的代码而不必过多操心底层技术细节。
1459

被折叠的 条评论
为什么被折叠?



