AQS全称AbstractQueuedSynchronizer抽象队列同步器,它提供了一个FIFO的队列用于实现同步锁以及其他涉及同步锁的核心组件(例如JUC下的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等)
同步器主要支持实现
- 独占锁,只有一个线程访问资源(ReentrantLock)
- 共享锁,多个线程访问共享资源(ReentrantReadWriteLock)
AQS类图
AQS内部原理
AQS内部维护一个FIFO双向队列,每一个节点Node封装的是竞争锁失败的线程以及线程等待的状态信息
双向链表特点:
可以从任意元素开始访问前驱和后继
AQS队列变化的过程
- 添加Node
如果当前线程竞争锁失败,则加入队列(进入锁池)
- 将新的Node加入队尾,原队列最后的一个元素next指向新元素,新Node的prev指向原队列最后的一个元素,然后新元素的next指向自己
- 通过CAS算法将tail指向新的Node(调用compareAndSetState方法)
- 释放Node
当持有对象锁的线程释放机锁,就会从队列中取出第一个Node对象(释放第一个阻塞的线程)
- 将AQS的head指向下一个节点
- 将新的节点prev更新为null
这个设置head的节点由获得锁的线程完成(也就是原第一个Node节点封装的线程),所以不需要用CAS算法设置head
AQS类使用说明
AQS是基于模板方法设计模式设计的,使用者需要继承同步器并重写指定的方法
重写时需要访问或者修改同步状态的方法
getState():获取当前同步状态。
setState(int newState):设置当前同步状态。
compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性
** 同步器可被重写的方法如下**
独占锁实现实例
public class Mutex implements Lock {
// 静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 当状态为0的时候获取锁
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁,将状态设置为0
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new
IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一个Condition,每个condition都包含了一个condition队列
Condition newCondition() { return new ConditionObject(); }
}
// 仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
public void lock() { sync.acquire(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public void unlock() { sync.release(1); }
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
除了部分方法重写之外,其余的方法全部委派给静态内部Sync类处理
AQS源码(独占式同步状态获取与释放)
Sync继承自AQS,它有两个实现类分别是:
- 公平锁(FairSync)所有线程按照FIFO顺序严格执行
- 非公平锁(FairSync)CLH队列中的线程和新线程都有机会获取锁
sync.lock();
默认调用的是非公平锁
NonFairLock,lock()
final void lock(){
if(compareAndSetState(0,1))//通过CAS操作修改对象锁state状态
setExclusiveOwnerThread(Thread.currentThread());//修改状态成功,设置获得锁的线程
else
acquire(1);//获取锁失败,尝试加入阻塞队列(尝试获取锁)
}
compareAndSetState()
底层调用的是sun.misc.Unsafe类的compareAndSwapInt方法,原理是CAS算法判断比较互斥量
- state=0,表示无锁
- state>0,表示有锁,由于锁可重入,同一个线程多次获取同步锁会使state递增(state=3),释放锁的时候,同样也需要释放多次(将state=3递减成为state=0)
acquire()
这个方法是AQS的方法,线程执行到这里,表示state!=0
- 如果tryAcquire尝试获取独占锁失败,那么就会调用addWaiter将线程封装成Node添加进AQS维护的CLH队列当中
- acquireQueued将Node作为参数,自旋获取锁
tryAcquire()在NonfairSync中的实现
addWaiter()和enq()
- 将当前线程封装为Node对象,添加入CLH队列
- 判断tail即CLH队尾是否为null,不为null则加入队尾并将tail指针指向新的Node
- tail为空说明队列为空,调用enq方法
- enq自旋判断队列队尾是否为Null,如果为null则添加一个空的Node节点为头节点
- 将新的Node节点前驱指向空的Node头,然后CAS比较将新的Node加入队尾
acquireQueued
- 当前线程节点的prev节点是否为head,如果是则将当前Node设置为head,并覆盖移除初始化的head节点
- 如果获取失败则根据waitStatus决定是否挂起,最后取消获取锁的操作
shouldParkAfterFaileedAcquire()
这个方法主要用于判断当前竞争锁失败的线程是否应该被阻塞,只有队列第二个Node可以竞争锁,对于其他节点应该继续阻塞
parkAndCheckInterrupt
如果判断当前线程应该被阻塞(返回true)时,就调用本方法,通过掉用LockSupport.park(和CAS操作类一样,也是来自于Unsafe)将线程挂起,里面只有俩方法
park()为获取一个permit,相当于消费一个许可
unpark()则是释放一个许可
有点类似Semaphore的许可,不同的是LockSupport里面的许可更贴近于互斥量(0/1),park调用时如果permit=0则方法会阻塞,unpark多次调用也不会改变许可的值
总结一下
AQS实现原理就是内部维护了一个CLH队列,将获取锁的线程封装成Node加入队列当中,初始化队列时会实例一个空的Node当作队头,每一次唤醒线程执行任务(运行线程释放了锁unlock->tryRelease())都会从第二个开始获取判断前驱是否是head,获取成功则把获取锁的线程设置为head,其余的Node节点一直处在自旋的状态中,阻塞节点时会判断队列是否为空,为空则先初始化,否则会直接将线程封装成Node添加至队尾,并用CAS算法将tail指向它