AbstractQueuedSynchronizer(AQS) 总结篇
简介
在之前已经有6篇关于AQS源码分析的文章了,关于源码分析的一些问题可以去看看我之前的文章,文章连接可以在文末查看。这一篇文章主要是对AQS的一些总结,或者说是面经。
AQS是什么
AQS 全称是AbstractQueuedSynchronizer,在java.util.concurrent.locks
包下面,是一个抽象的可以实现阻塞线程、排队控制、唤醒线程等操作的同步器基础框架类,AQS 可以实现排它锁、共享锁、条件锁、计数器等相关功能。
父类AbstractOwnableSynchronizer
AQS 继承的父类AbstractOwnableSynchronizer,该类仅一个属性用于记录当前持有锁的线程,提供get/set方法。
变量:同步状态state
state 字段是一个非常重要的字段,可以基于state字段的值定义出不同的同步锁功能,比如:
- 基于state 的值实现排他锁
state 值为1代表锁被占用,值为0时代表锁未被占用。
代表类:ReentrantLock - 基于state的值实现读写锁
state 被分成两部分,高16位记录读锁次数,低16位记录写锁次数
代表类:ReentrantReadWriteLock - 基于state的值实现限制线程数
初始化一个state值,表示最大限制数,即可以做到允许最多N个线程同时运行,达到限流效果
代表类:Semaphore - 基于state的值实现倒计数
初始化一个state值,state值为0时触发唤醒动作
代表类:CountDownLatch
两个队列
AQS 里面有两个队列,我称为同步队列和条件队列。条件队列主要是实现条件锁时用到的队列,同步队列就是维护唤醒线程的队列。
- 同步队列
主要用于维护获取互斥锁失败时入队的线程 - 条件队列
调用await()
的时候会释放锁,然后线程会加入到条件队列,调用signal()
唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁
可以重写的API
AQS 提供了 5 个可以自定义实现功能的API方法,基于这些方法,则可以实现不同类型的锁功能。
- protected boolean tryAcquire(int arg)
尝试一次获得一个排它锁 - protected boolean tryRelease(int arg)
尝试一次释放一个排它锁 - protected int tryAcquireShared(int arg)
尝试一次获得一个共享锁 - protected boolean tryReleaseShared(int arg)
尝试一次释放一个共享锁 - protected boolean isHeldExclusively()
验证排它锁是否被占用
提供的模版方法
下面的这些模版方法,都用到了上面可以重写的API方法。
-
基于
tryAcquire
API提供的模版方法-
获得一个排它锁,直到成功获得锁
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
-
获得一个排它锁,可被中断
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
-
获得一个排它锁,可超时或中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
其中
tryAcquire(arg)
方法是需要自己实现的方法 -
-
基于tryAcquireShared API提供的模版方法
-
获得一个共享锁,直到成功获得锁
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
其中
tryAcquireShared(arg)
方法是需要自己实现的方法 -
获得一个共享锁,可被中断
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
-
获得一个共享锁,支持超时或中断
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); }
-
-
释放一个排它锁
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
其中
tryRelease(arg)
方法是需要自己实现的方法 -
释放一个共享锁
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
其中
tryReleaseShared(arg)
方法是需要自己实现的方法
节点状态
AQS 定义了5个队列中节点状态:
- 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
- CANCELLED,值为1,表示当前的线程被取消;
- SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
- CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
- PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
其他
还有一个与AQS非常相似的类——AbstractQueuedLongSynchronizer,从命名上来看,多了一个Long,从源码上来看,他们两个有完全相同的结构、属性和方法,唯一不同之处就在于所有与状态相关的参数和结果都定于为long类型,而不是int类型,当需要创建64位状态的同步器(例如多级锁和屏障)时,AbstractQueuedLongSynchronizer类可能很有用。