一.AQS也是java中相对底层的设计框架:AbstractQueuedSynchronizer(抽象队列化同步器),它是个抽象类,不过已经实现了几乎所有的核心方法, 它主要用于需要对某一状态(state)基于类似计数器形式的同步操作,此计数器用来表示“锁”的形式或者状态。
AQS持有一个volatile int state的属性,表示同步的状态。对于state的修改需要基于CAS方式。 AQS主要是使用CAS方式修改数据,使用LockSupport来“剥夺”/"释放"线程,使用queue来保存“获取锁”、“等待锁”的线程。
AQS支持2中模式:共享模式、独占模式;这个和“共享锁”和“独占锁”的概念几乎一致,在java中“共享锁”、“独占锁”的功能也是通过 AQS来实现的。 一般情况下,AQS是实现类,只支持2种模式中的一种,也可以同时支持2种。在独占模式下,其他线程试图获取锁将无法成功;在共享模式下,多个线程 获取某个锁可能(但不一定)会获得成功。
如下是AQS的抽象方法:
- boolean tryAcquire(int)
- boolean tryRelease(int)
- int tryAcquireShared(int)
- boolean tryReleaseShared(int)
- isHeldExclusively()
前四个是分别是独占模式/共享模式下需要实现的方法,默认这些方法会抛出UnsupportedOperationException(设计的初衷是期望子类来实现)。 其中tryAcquire(int)、tryAcquireShared(int),是可以设计为允许或者禁止闯入行为的,用来设计基于公平或者非公平策略;对于非公平策略下,通常是吞吐量和伸缩性最好的。 “禁止闯入”行为的方式,是需要在获取锁之前,首先将当前线程加入队列,并判断队列中Head的阻塞线程是否是自己即可。。 boolean tryAcquire(int) int tryAcquireShared(int) 这两个方法,都是“试图获取锁”行为,对于此方法的要求是:返回true表示获取成功且已经持有锁,表示false表示获取失败,在false情况下,AQS会将线程加入到等待队列。 对于tryAcquireShared将会返回一个int,失败时返回负值,如果获取成功且其后共享模式下不能获取成功,则返回0,如果后续可能返回成功则返回正值,当对共享锁的获取个数有上限时,有用。;如果返回false,AQS将会做一些措施(比如将线程信息队列化) .
boolean tryRelease(int) boolean tryReleaseShared(int) 释放锁。锁的信息会以node的方式存在queue中,释放锁其实就是让当前node离队,并让下一个节点“触发”(阻塞的线程被唤醒)。 这两个方法需要告知AQS当前state是否可以释放锁;如果返回true,AQS将会做相应的处理(比如让下一个等到的线程唤醒)。 AQS本身有很多方法可供使用,上述几个抽象方法,最终被AQS所调用。 比如acquire()会调用tryAcquire来检测是否可以获取锁等。
AQS的开闭原则,做的很不错。 基于AQS的实现有如下几个API:
- CountDownLatch:基于共享模式的同步(基于信号量)
- Semaphore:基于共享模式下的同步,其提供了“公平”和“非公平”2种策略(基于信号量)
- ReentrantLock、ReentrantReadWriteLock:基于独占模式下的同步,其提供了“公平”和“非公平”2种策略。
二.源码分析
公平策略:对“锁”的获取是公平,任何对锁的请求,首先加入队列,然后校验队列中的即将“触发”的锁获取者是否是当前线程。
2.1)共享模式下的公平策略(摘自semaphore):
semaphore思想描述:acquire会导致信号减弱(锁计数器减少),release导致信号增强,信号<0导致acquire阻塞
release时如果信号>0,会触发等待队列”触发“中线程"unpark"(解除阻塞)。
protected int tryAcquireShared(int acquires) {
Thread current = Thread.currentThread();
for (;;) {
Thread first = getFirstQueuedThread();//获取队列的头
if (first != null && first != current)
return -1;//获取失败
int available = getState();//信号剩余量
int remaining = available - acquires;
//当剩余信号<0时,获取失败(返回负值)
//在有信号且CAS成功时,获取成功(信号减弱)
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
在共享模式下,释放锁,就很简单,自旋,直到当前线程持有的”信号“被CAS成功:设计核心就是千万不可导致信号"丢失":
protected final boolean tryReleaseShared(int releases) {
//确保"释放"成功
for (;;) {
int p = getState();
if (compareAndSetState(p, p + releases))
return true;
}
}
共享模式:只要锁计数器允许,多个线程都可以获取成功,在共享模式下,一般计数器state会>1.
2.2)独占模式下公平策略(摘自ReentrantLock):
支持重入特性的独占锁。acquire将会导致锁计数器增加,release将会导致锁计数器减小。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//支持重入,线程实例作为比较"标记"
int c = getState();
//如果锁计数器为0,且是首个获取锁的线程,则CAS计数器
//因为state默认为0,第一个获取锁操作,需要特殊处理
//如果state==0,且不是第一个操作,获取锁失败
if (c == 0) {
if (isFirst(current) &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//对于独占模式下,此操作时必须的,很多时候用来判断自己是否为锁持有者,在此处设置"独占锁"持有线程信息
return true;
}
}//支持重入的利器,判断当前线程是否为锁持有者,如果是,则可以继续获取锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;//锁引用数量仍然会被计算
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
独占模式下,释放锁和共享模式有些不同,释放锁操作一般和”获取锁“是呼应的,锁释放者必须是当前锁的持有者。
///释放锁,不仅要修改state计数器,还有重置”锁持有者“信息(setExclusiveOwnerThread(null))
protected final boolean tryRelease(int releases) {
int c = getState() - releases;///锁引用计数器减小
检测是否是独占锁的持有者
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
因为当前线程为执行者,可以直接setState,无需CAS
setState(c);
return free;
}
2.3) 共享模式下,非公平策略(摘自semaphore):
共享模式下,非公平策略下,获取共享锁;从代码中我们可以看到共享模式下的非公平锁的获取非常随意,只关心了锁引用数量的正确"增加".
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//共享模式下,无需判断线程独占问题。
int available = getState();
int remaining = available - acquires;
//如果锁信号剩余量<0,或者CAS成功,则返回,根据剩余量来决定是否排队
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
共享模式下,非公平策略下,释放共享锁仍然很简单,如果CAS信号量成功,即释放成功。(其实释放锁,与公平性无关,只与独占与否有关)
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int p = getState();
if (compareAndSetState(p, p + releases))
return true;
}
}
2.4)独占模式下,非公平策略(摘自ReentrantLock):
//获取锁
final boolean nonfairTryAcquire(int acquires) {
//独占的特点,就是首先锁持有者,是否为自己
final Thread current = Thread.currentThread();
int c = getState();
//如果首次获取锁,如果CAS成功,则标记独占信息。
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}//否则,检测当前锁持有者是否为自己,如果是,则重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//释放锁:特点就是校验自己是否为锁占有者,只有占有者才能释放锁。释放锁,与公平性无关。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
上述方法,是AQS类本身没有实现,子类为了完成各自的功能需要单独实现.在子类中对上述方法的实现大同小异,在此不再赘言.
除了上述方法,AQS还提供了类似"happen-before"的后续方法,这些方法已经被AQS实现,且不能被重写,AQS将会使用这些方法完成获取锁失败/释放锁成功之后的队列操作,例如获取锁失败,将线程阻塞并队列化,释放锁成功,"触发"阻塞队列中的线程继续工作等:
- doAcquireShared(int arg):如果当前子类的tryAcquireShared返回-1信号(即获取失败),则执行此方法,则队列化并阻塞当前线程
- doReleaseShared():如果当期那子类的tryReleaseShared返回true(即锁释放成功,不过我们理解为锁引用计数重置成功)执行,则执行此方法,将会触发等待锁的线程被唤醒.
- doAcquireSharedInterruptibly(int arg):方法同doAcquireShared,它额外的支持了对线程中断状态的检测和处理;如果在获取锁过程中(包括获取前/以及实际上获取成功后)线程中断,那么就抛出Interrupted异常.
- doAcquireInterruptibly(int arg):同上,只是此方法用于获取独占锁.
AQS是个非常底层的类,它内部还有很多"神秘"的地方值得我们去学习,比如"队列化"线程的过程/"触发"线程唤醒的过程...