大一菜鸡的个人笔记,欢迎指点和交流。
想要学透AQS非常困难,希望之后多看源码和更新笔记…
AQS
AbstractQueuedSynchronizer,提供一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类都是基于AQS实现的,通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。
AQS维护了一个volatile语义(支持多线程下的可见性)的共享变量state和一个FIFO线程等待队列(多线程竞争state时会被阻塞进入此队列)
在基于AQS构建的同步器类中,最基本的操作包括各种形式的获取操作和释放操作,获取操作是一种依赖状态的操作,并且通常会阻塞。当使用锁或信号量的时候,获取操作的含义就是获取锁或许可,并且调用者可能会一直等待直到同步器类处于可被获取的状态在使用CountDownLatch时,"获取"操作表示等待并直到闭锁达到结束状态。
核心思想
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列之中。
实现ReentrantLock这种类的内核,ReentrantLock只是一个外层的API。AQS其实就是一个并发包的基础组件,用来实现各种锁,同步组件。包含了state变量、加锁线程、等待队列等。
CLH队列
Craig Landin and Hagersten 队列是一个虚拟的双向队列,即不存在队列实例,仅存在节点之间的关联关系。
exclusiveOwnerThread
用来记录上锁的线程是哪个,初始化状态是null。解锁后也会变为null。
State
state的数据类型是int,代表了锁的状态。有三种访问方式。初始情况是0。上n次锁,则state为n。只有检查锁属于该线程才可以多次上锁。加锁失败则进入等待队列。解锁则state-1.
- getState()
- setState(int newState)
- compareAndSetState(int expect,int update)
以上三种都是原子操作(不能被分割,要么同时成功要么同时失败)。其中compareAndSetState()的实现依赖于Unsafe的compareAndSwapInt()方法。
上锁的过程:用CAS操作把state值从0变为1.
资源的共享方式
独占式(Exclusive)
只有单个线程能够成功获取资源并执行,如ReentrantLock等
共享式(Shared)
多个线程可成功获取资源并执行,如Semaphore/CountDownLatch等
AQS将大部分的同步逻辑实现好,继承的自定义同步器只需要实现state的获取(acquire)和释放(release)的逻辑代码。
AQS的使用方式
在类内部实现sync类,然后sync类继承AQS,实现其中的一些方法。
AQS的比喻
Semaphore:一个人面试完之后,后一个人才能进来继续面试。
CountDownLatch:群面,等待10人到齐。
Semaphore、CountDownLatch这些同步工具类,要做的,就是写下自己的要人规则。
资料来源:https://zhuanlan.zhihu.com/p/86072774
Java并发编程实战