一、AQS简介
队列同步器AbstractQueuedSynchronizer(简称AQS)是用来构建锁或其他同步组件的基础框架,它服务的是锁的实现者。AQS有一个变量表示同步状态,通过内置的FIFO管理线程排队,基于AQS可以将同步状态管理、线程排队、等待与唤醒等操作对锁屏蔽,简化锁的实现方式。
同步器的设计是基于模板方法的,使用者需要重写同步器指定的方法,然后将同步器组合在自定义同步组件的视线中,并调用同步器提供的模板方法。AQS的模板方法包括获取和释放同步状态等。
二、AQS原理
理解AQS主要是理解同步队列管理和同步状态的获取与释放两点。
1、队列同步器结构
AQS有两个节点的引用head和tail,分别指向头尾节点。
入队设置尾节点:获取同步状态(或者说获取锁)失败的线程,会被作为节点加入队列,为保证加入过程的线程安全,通过compareAndSetTail方式入队。(第一次设置头结点是在这个操作中)
出队设置头结点:由于只有一个线程能够成功获取到同步状态,因此设置头结点不需要CAS保证,只需将头节点的下一个节点设为头结点。
2、同步状态的获取与释放
获取过程调用方法acquire:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
获取成功时做两件事:一是更新同步状态+1,二是setExclusiveOwnerThread设置获得同步状态的线程为当前线程。
获取逻辑:通过调用同步器方法tryAcquire方法,尝试获取同步状态,如果获取失败,则构造同步节点,并CAS调用addWaiter方法尝试将节点加入同步队列尾部。同步队列中的节点线程调用acquireQueued方法,以死循环的方式获取同步状态。如果获取不到同步状态,则阻塞节点中的线程,唤醒线程的方式是获取同步状态成功或线程被中断。获取过程如下图:
释放过程调用release方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
释放成功时做两件事:一是更新同步状态-1,二是setExclusiveOwnerThread(null)设置获得同步状态的线程为null。
释放逻辑:尝试释放,如果成功则唤醒后继节点线程。
3、获取同步状态的方式
获取同步状态的方式有3种:独占式获取同步状态、共享式获取同步状态太、独占式超时获取同步状态。
三者的区别在于,独占式同一时刻只会有一个线程获得到同步状态,而共享式可以有多个线程获得同步状态。独占式超时获取同步状态,如果获取不成功,在超时后会放弃而不会一直阻塞(这也是Lock相对于synchronized增加的功能)。
参考资料:
《Java并发编程的艺术》