AQS是AbstractQueuedSynchronizer类的简写, 它提供了一套基础的同步框架, 可以根据自定义扩展. AQS细节较多, 今天只从宏观的角度, 看下它是怎样设计实现的.
核心设计
首先, AQS是基于以下两个核心做的设计:
1.双向队列(CLH队列)
CLH队列全称是(Craig, Landin, andHagersten) lock queue, 用来存储被阻塞的线程信息.
2.资源状态state
在独占锁的场景下, 可以理解为资源占有的标识位; 在共享锁的情况下, 可以作为资源数量理解.
独占锁与共享锁
AQS中提供了两套锁机制: 共享锁与独占锁
共享锁:
可以有多个线程同时执行, 线程执行个数也会受到state限制, 如Semaphore,CountDownLatch等等
独占锁:
当前只有一个线程能够运行, 如ReentrantLock;
独占锁又分为公平锁(FairSync)和非公平锁(NonfairSync)
公平锁: 所有线程严格按照FIFO占有锁.
final void lock() {
acquire(1);
}
非公平锁: 每个线程不管是否有其他线程等待, 都会先尝试抢占锁, 抢占失败再去CLH队列中等待锁资源释放.
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
CAS操作
AQS在获得资源状态state, 以及添加阻塞线程到CLH队尾时都是使用CAS操作解决线程间资源抢占问题的.
1. 获取资源:
compareAndSetState(0, 1)
2. 添加线程到CLH队尾
Node中封装线程及锁独占或共享标识
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
线程阻塞及唤醒
在AQS中, 当线程抢占资源失败时, 会调用unsafe.park()方法阻塞线程;
当其他线程释放资源时, 也会调用unsafe.unpark()唤醒线程;
模板模式
为方便扩展应用, AQS内部已经实现了CLH队列, 线程的阻塞唤醒等复杂逻辑, 我们只需要根据需要完成独占模式或共享模式下的状态同步方法即可.
tryAcquire: 独占模式下获取同步资源
tryRelease: 独占模式下释放同步资源
isHeldExclusively: 独占模式下查看当前线程是否获取同步资源
tryAcquireShared: 共享模式下获取同步资源
tryReleaseShared: 共享模式下释放同步资源
AQS处理流程
有了上述各实现功能, 我们可以猜想的到AQS的处理流程了, 当然实际情况会复杂的多.