1 概述
AQS是AbstratQueuedSynchronizer的缩写,即抽象同步列,JUC中锁的底层就是基于该抽象类实现的。其类图如下:
该类是一个FIFO的双向队列,队列中的元素时Node(是AQS的静态内部类)。
Node中的thread变量用来存放进入AQS的线程,Node中的SHARE用来标记该线程是获取共享资源时被挂入AQS的;EXCLUSIVE是用来标记该线程是获取独占资源时被挂入AQS的;waitStatus可以用来表示该线程的状态:线程被取消了CANCELLED,线程被挂在条件队列上CONDITION,线程需要被唤醒SIGNAL,释放共享资源时需要通知其他节点PROPAGATE;prev用来记录当前节点的前驱结点;next用来记录当前节点的后驱节点。
ConditionObject是AQS的内部类,是一个条件变量,用来结合锁实现线程同步,后面将详细介绍。
AQS中还有一个状态变量state,该变量可以用来表示锁的一些状态,比如对于独占锁ReentrantLock,state可以用来表示其可重入次数,对于ReentrantReadWriteLock来说,state的高十六位表示读状态,也就是读锁的获取次数,低十六位用来表示写锁的可重入次数。
2 主要方法
线程同步的关键就是对state状态变量进行操作,而操作state又可以分为独占方式和共享方式,独占方式下获取和释放资源的方式是:void acquire(int arg), void acquireInterruptibly(int arg), boolean release(int arg);共享方式下获取和释放资源的方式是void acquireShared(int arg), void acquireSharedInterruptibly(int arg), boolean releaseShared(int arg)。下面主要对独占方式下的acquire(int arg)方法和release(int arg)方法进行介绍。
2.1 acquire(int arg)
首先来看以下该方法的源码,首先尝试获取独占资源,获取失败则将自己挂到阻塞队列尾部
public final void acquire(int arg) {
//tryAcquire()是尝试获取资源的方法,由AQS具体的子类来实现
if (!tryAcquire(arg) &&//tryAcquire()尝试获取资源失败进入以下判断逻辑
acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
selfInterrupt();
}
然后再接着看addWaiter()方法的源码
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
//创建状态为mode(EXCLUSIVE)的节点,并将当前线程放入node中
AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
AbstractQueuedSynchronizer.Node pred = tail;//获取当前AQS队列的尾部节点
if (pred != null) {//当前尾部节点不是null
node.prev = pred;//将要添加的node的前驱节点设置为pred
if (compareAndSetTail(pred, node)) {//将当前AQS队列的tail指针指向node
pred.next = node;//将pred的后驱节点设置为node
return node;//返回该node
}
}
//若此时的尾节点为空,则说明此时AQS中队列没有元素,则执行以下方法将node添加到队列中
enq(node);
return node;
}
private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
for (;;) {//自旋操作
//t指针指向AQS尾节点tail
AbstractQueuedSynchronizer.Node t = tail;
if (t == null) { /*若尾节点为null则说明当前队列中没有元素
(这里注意虽然addWaiter()中判断了一次,但由于程序执行过程中并没有加锁,
故在此处需要再次判断,防止期间有其他线程添加了节点)*/
//利用CAS操作设置头结点(该节点是一个虚节点,或者说哨兵节点,内部并没有相应线程)
if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
tail = head;//将tail也指向哨兵节点后进入下一次循环(此时还未将真正要添加的节点添加)
} else {//在第二次循环时开始添加node
node.prev = t;//将node的前驱节点设置为t,此时t指向哨兵节点
if (compareAndSetTail(t, node)) {//利用CAS设置node为尾节点
t.next = node;//将t(此时指向哨兵节点)的后驱节点设置为node
return t;//返回t
}
}
}
}
至此,我们就已经将尝试获取独占资源失败的节点放入到了AQS阻塞队列的尾部,下面再来看外层的acquireQueued()方法:
final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//自旋操作
final AbstractQueuedSynchronizer.Node p = node.predecessor();//获取该节点的前驱节点
if (p == head && tryAcquire(arg)) {//如果该节点的前驱节点是哨兵节点且请求资源成功则进入以下逻辑
setHead(node);//将该节点设置为哨兵节点,具体操作看源码一目了然
/*private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
* */
p.next = null; // help GC
failed = false;
return interrupted;
}
//上述逻辑判断失败则进入下面的判断逻辑
// shouldParkAfterFailedAcquire()是根据当前节点前驱节点的状态判断是否阻塞该节点
//parkAndCheckInterrupt()则是阻塞该节点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//请求资源失败,当前节点放弃获取资源
cancelAcquire(node);
}
}
2.2 release(int arg)
来看看该方法的源码:
public final boolean release(int arg) {
//尝试释放独占资源,该方法也是由AQS具体子类来实现
if (tryRelease(arg)) {
//获取当前队列的头结点
AbstractQueuedSynchronizer.Node h = head;
//激活AQS队列中的一个线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3 条件变量支持ConditionObject
AQS中的条件变量就和synchronized(object)中的object一样,内部有两个重要的方法signal()和await()方法,就好比synchronized中的notify()和wait()方法一样;调用条件变量await()方法的线程会被挂到条件变量的阻塞队列中(这是一个单向链表),调用条件变量signal()方法的线程会唤醒该条件变量阻塞队列中的一个线程。下面来看一个例子:
public class Test {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 调用条件变量的await()方法被阻塞了.....");
try {
condition.await();
System.out.println(Thread.currentThread().getName() + " 被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
try {
Thread.sleep(1000);
System.out.println("main尝试获取锁");
lock.lock();//注意使用条件变量是一定要先获取该条件变量所对应的锁,否则会抛出IllegalMonitorStateException异常
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("main is over");
}
}
整体过程就是thread首先获取锁后调用条件变量的await()方法将自己挂到condition的条件队列上并释放了锁,然后main线程获得所后调用condition的signal()方法,唤醒了一个条件队列上的线程,也就是thread(注意,此时还是main线程获取了锁,thread从条件队列移动到了AQS阻塞队列上等待获取资源),当main线程释放锁后,thread获取到锁后继续执行后面的代码。