AQS
1、AQS的产生背景
通过JCP的JSR166规范,Jdk1.5开始引入了j.u.c包,这个包提供了一系列支持并发的组件。这些组件是一系列的同步器,这些同步器主要维护着以下几个功能:内部同步状态的管理(例如表示一个锁的状态是获取还是释放),同步状态的更新和检查操作,且至少有一个方法会导致调用线程在同步状态被获取时阻塞,以及在其他线程改变这个同步状态时解除线程的阻塞。上述的这些的实际例子包括:互斥排它锁的不同形式、读写锁、信号量、屏障、Future、事件指示器以及传送队列等。可以看下这里图便能理解 j.u.c包的组件构成。
几乎任一同步器都可以用来实现其他形式的同步器。例如,可以用可重入锁实现信号量或者用信号 量实现可重入锁。但是,这样做带来的复杂性、开销及不灵活使j.u.c最多只能是一个二流工程,且缺乏吸引力。如果任何这样的构造方式不能在本质上比其他形式更简洁,那么开发者就不应该随意地选择其中的某个来构建另一个同步器。因此,JSR166基于AQS类建立了一个小框架,这个框架为构造同步器提供一种通用的机制,并且被j.u.c包中大部分类使用,同时很多用户也可以用它来定义自己的同步器。这个就是j.u.c的作者Doug Lea大神的初衷,通过提供AQS这个基础组件来构建j.u.c的各种工具类,至此就可以理解AQS的产生背景了。
2、AQS的设计和结构
2.1 设计思想
同步器的核心方法是acquire和release操作,其背后的思想也比较简洁明确。
- acquire操作是这样的:
while (当前同步器的状态不允许获取操作) { 如果当前线程不在队列中,则将其插入队列 阻塞当前线程 } 如果线程位于队列中,则将其移出队列
- release操作是这样的:
更新同步器的状态 if (新的状态允许某个被阻塞的线程获取成功) 解除队列中一个或多个线程的阻塞状态
从这两个操作中的思想中我们可以提取出三大关键操作:同步器的状态变更、线程阻塞和释放、插入和移出队列。所以为了实现这两个操作,需要协调三大关键操作引申出来的三个基本组件:
- 同步器状态的原子性管理;
- 线程阻塞与解除阻塞;
- 队列的管理;
由这三个基本组件,我们来看j.u.c是怎么设计的。
2.1.1 同步状态
AQS类使用单个int(32位)来保存同步状态,并暴露出getState、setState以及compareAndSet 操作来读取和更新这个同步状态。其中属性state被声明为volatile,并且通过使用CAS指令来实现 compareAndSetState,使得当且仅当同步状态拥有一个一致的期望值的时候,才会被原子地设置成新 值,这样就达到了同步状态的原子性管理,确保了同步状态的原子性、可见性和有序性。
基于AQS的具体实现类(如锁、信号量等)必须根据暴露出的状态相关的方法定义tryAcquire和 tryRelease方法,以控制acquire和release操作。当同步状态满足时,tryAcquire方法必须返回true, 而当新的同步状态允许后续acquire时,tryRelease方法也必须返回true。这些方法都接受一个int类型的参数用于传递想要的状态。
2.1.2 阻塞
。。。。。。