队列同步器(以下简称AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,是实现大部分同步需求的基础。
AQS的主要使用方式是继承,子类通过继承AQS并实现它的可重写方法来管理同步状态,在可重写方法实现过程中免不了对同步状态进行更改,这个时候就需要使用AQS提供的三个方法(getState()、setState(int newState)、compareAndSetState(int expect, int update))来进行操作,这三个方法能保证状态的改变是安全的。子类推荐被定义为自定义同步组件的静态内部类,AQS自身没有实现任何同步接口,他仅仅定义了若干同步状态获取和释放的方法来供自定义同步组件(锁等)使用,AQS既可以支持独占式地获取同步状态,也可以共享式地获取同步状态,这样就可以方便实现不同类型的同步组件。
AQS接口与示例
接口:
AQS的实现采用的是模板模式,对外提供的三类方法用于实现一个同步组件;
三类方法关系:使用者继承AQS并重写五个指定方法(第二类)。重写AQS指定方法时,需要使用AQS提供红的三个方法来访问或者修改同步状态(第一类)。最后将AQS组合在自定义同步组件的实现中,调用其9个模板方法(第三类)与第二类中重写过的5个方法来实现另外模板方法会调用使用者重写的方法。
第一类:3个访问和修改同步状态的方法
重写AQS指定的方法第二类时,需要使用AQS提供的如下三个方法来访问或者修改同步状态。
- getState():获取当前的同步状态
- setState(int newState):设置房前同步状态
- compareAndSetState(int except, int update):使用CAS设置房前状态,该方法能保证状态设置的原子性
第二类:5个可重写方法
- isHeldExclusively():该线程是否在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared:共享方式。尝试获取资源。返回负数表示失败。返回0表示成功,但没有剩余可用资源。返回正数则表示成功,且有剩余资源
- tryReleaseShared:共享方式。尝试释放资源。成功则返回true,失败则返回false。
第三类:9个模板方法
模板方法主要分成:独占式获取与释放同步状态,共享式获取与释放同步状态,查询同步队列中的等待线程。这九个模板方法不可被重现,但他们调用上述的五个可重写方法,并定制功能实现的细节。
- void acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则有该方法返。否则将会进入同步队列等待,该方法会调用重写的tryAcquire(int)方法
- void acquireInterruptibly(int arg):与acquire(int arg)相同,但是方法响应中断。当前线程为获取到同步状态而进入同步队列,如果当前线程被中断,则该方法会抛出InterruptedException
- boolean tryAcquireNanos(int arg, long nanos):在acquireInterruptibly(int arg)基础上增加了超时机制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,如果获取到了则会返回true
- void acquireShared(int arg):共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
- void acquireSharedInterruptibly(int arg):在acquireShared(int arg)基础上增加响应中断
- boolean tryAcquireSharedNanos(int arg, long nanos):在acquireSharedInterruptibly(int arg)基础上增加超时机制
- boolean release(int arg):独占式的释放同步状态,该方法会在释放同步状态后,将同步队列中的第一个节点唤醒
- boolean releaseShared(int arg):共享式的释放同步状态
- Collection<Thread> getQueueThreads():获取等待在同步队列上的线程集合
示例:自定义独占锁实现
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();
@Override
public void lock() { sync.acquire(1); }
@Override
public boolean tryLock() { return sync.tryAcquire(1); }
@Override
public void unlock() { sync.release(1); }
@Override
public Condition newCondition() { return sync.newCondition();}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
}
分析:
- 首先定义了一个静态内部类Sync,并重写了AbstractQueuedSynchronizer的部分方法;
- 实现Lock接口:实现Lock接口定义的6个方法,实现方法可体现基本就是直接代理到AQS的Sync上的模板方法(第三类)或重写过的方法(第二类)来完成实现。
总结:实现一个自定义锁步骤:
- 继承Lock接口
- 定义一个AQS子类,作为锁的静态内部类。该类继承自AQS,也有用了AQS定义的9个模板方法。另外还需要实现AQS定义的5个可重写方法,重写实现过程中需要用到3个同步状态的修改方法。
- 实现Lock规定的接口方法,原理是将“操作”代理到AQS的子类Sync上,即利用Sync的14个方法来实现Lock
AQS的实现原理
同步队列
队列节点:节点node是构成同步队列的基础,获取同步状态失败的线程将会被包装成一个节点加入同步队列的尾部
- int waitStatus:等待状态。包含以下状态:
- CANCELLED:值为1,由于在同步队列中等待的线程超时或被中断,需要从同步队列中取消等待。节点变为该状态后不会再变化
- SIGNAL:值为-1,后记节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点
- CONDITION:值为-2,节点再等待队列中,节点线程等待再Condition上。当其他线程对Condition调用signal方法后,该节点将从等待队列转移到同步队列中,加入同步状态的获取中
- PROPAGATE:值为-3,表示下一个共享式同步状态将会无条件的传播下去(主要应用在“共享”模式下)
- INITIAL:值为0,初始状态
- Node prev:前驱节点
- Node next:后继节点
- Node nextWaiter:等待队列中的后继节点
- Thread thread:获取同步状态的线程
队列基本结构:AQS包含两个节点的引用(头,尾节点)

新加入节点的过程:当有新节点需要加入的时候,需要保证线程安全,AAQS提供了一个基于CAS的设置为节点的方法:compareAndSetTail(Node except, Node update),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。

首节点释放过程:同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程再释放同步状态时,会唤醒后继节点,而后继节点会在获取同步状态成功时将自己及设置为首节点

独占式同步获取与释放
在获取同步状态时,AQS维护了一个同步队列,获取状态失败的线程都会被加入到队列中,并阿紫队列中自旋。在释放同步状态时,AQS调用tryRelease方法释放同步状态,然后唤醒后继节点。移出队列的条件时后继节点获取了同步状态,并把自己设置成了新的头节点,此时前驱节点会被移出队列。

共享式同步获取与释放
共享式获取与独占式获取的差别在于同一时刻是否能让多个线程获取同步状态。共享式访问时,其他共享式的访问都会被允许,而独占式访问时被阻塞。独占式访问资源时,同一时刻其他访问均被阻塞。
共享式同步获取实现:在accquireShared(int arg)中,同步器调用tryAccqurieShared(int arg)方法尝试获取同步状态,tryAccqurieShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能获取到同步状态。因此在共享式获取的自选过程中,成功获取到同步状态并推出自旋的条件是tryAccqurieShared(int arg)方法返回值大于等于0
共享式同步状态释放:通过调用releaseShared(int arg)方法可以释放同步状态
独占式超时获取与释放
使用理解:通过调用AQS的doAcquireNanos(int arg, long nanosTimeout)方法可以超时获取同步状态,即在指定的时间段内获取同步状态,如果获取到同步状态则反悔true,否则返回false。该方法提供了传统Java同步操作(如Synchronized关键字)不具备的特性
支持中断:超时获取同步状态过程可以被视作为响应中断获取同步状态过程的“增强版”,doAcquireNanos(int arg, long nanosTimeout)方法在支持响应中断的基础上,增加了超时获取的特性
设置时间过短:如果nanosTimeout小于等于spinForTimeoutThreshold(1000纳秒)时,将不会使该线程进行超时等待,而是进入快速的自选过程。原因在于,非常短的超时等待无法做到十分精确,如果这时在进行超时等待,反而会让超时从整体上表现得反而不精确
