AQS
AQS介绍
java.util.concurrent.locks
包中的 AbstractQueuedSynchronizer (抽象队列同步器)类
该类是用于构建锁和同步器的框架。 使用该类可以简单且高效的构造出应用广泛的同步器,比如:ReentrantLock, Semaphore, ReentrantReadWriteLock, FutureTask等。同样我们也能继承该类,去实现自己需求的同步器。
AQS原理
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程归为有效工作线程,并且该资源设置为锁定状态。其他线程如果要请求该共享资源,由于该资源被占有,因此无法请求成功,那么就需要一套线程阻塞等待以及唤醒时锁分配的机制。这个机制AQS使用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
在AbstractQueuedSynchronizer类中有介绍CLH(Craig, Landin, and Hagersten),CLH队列是一个虚拟的双向队列(虚拟的双向对立是不存在队列实例,而是通过Node节点之间的关系关联)
AQS原理图:
AQS使用int类型的state标示锁的状态。使用CAS对同步状态进行原子性操作实现状态修改。
private volatile int state; // The synchronization state. volatile保证线程可见
获取同步状态的源代码:
// 获取同步的状态
protected final int getState() {
return state;
}
// 设置同步的状态
protected final void setState(int newState) {
state = newState;
}
// 通过CAS设置同步的状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS中Node节点常量含义
volatile int waitStatus;
int CANCELLED = 1;//waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞。
int SIGNAL = -1;//waitStatus为-1时表示该线程的后续线程需要阻塞,即只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程
int CONDITION = -2; //waitStatus为-2时,表示该线程在condition队列中阻塞(Condition有使用)
int PROPAGATE = -3;//waitStatus为-3时,表示该线程以及后续线程进行无条件传播(CountDownLatch中有使用)共享模式下, PROPAGATE 状态的线程处于可运行状态
waiteStatus 为 0: None of the above // 初始状态
AQS定义两种资源共享方式
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
-
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
-
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock的Read锁。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在底层实现好了。
AQS底层用模板方法模式
AQS底层是模板方法模式的,如果需要自定义同步器,一般方法是:继承AQS,并重写指定方法(无非是按照自己定义的规则对state的获取与释放);将AQS组合在自定义同步组件的实现中,并调用模板方法,而这些模板方法会调用重写的方法。
需要重写的方法:
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
以上方法默认抛出UnsupportedOperationException
异常,AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。