Java并发包中锁的底层都是使用AQS实现的,今天开始堆AQS进行简单的分析。AQS是AbstractQueuedSynchronizer的简称,继承自AbstractOwnableSynchronizer,有两个内部类Node和ConditionObject,这大致就是AQS的内部结构了。
AQS 是一个FIFO 的双向队列,队列元素的类型内部类Node,Node内部类如下。
static final class Node {
//用来标记该线程是获取共享资源时被阻塞挂起后放入A QS 队列的,
static final Node SHARED = new Node();
//用来标记线程是获取独占资源时被挂起后放入AQS 队列的
static final Node EXCLUSIVE = null;
//当前线程被取消了
static final int CANCELLED = 1;
//线程被唤醒
static final int SIGNAL = -1;
//记录当前线程在条件队列里等待
static final int CONDITION = -2;
//放共享资源时需要通知其他节点
static final int PROPAGATE = -3;
//记录当前线程等待状态
volatile int waitStatus;
//存放当前线程
volatile Thread thread;
内部类ConditionObject用来结合锁实现线程同步,ConditionObject可以直接访问AQS 对象内部的变量,比如state状态值和AQS 队列。ConditionObject 是条件变量, 每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await 方法后被阻塞的线程。
了解了AQS的内部结构,下面分析下AQS的工作原理。首先AQS主要用于辅助锁的实现,那么锁的获取和释放是怎样的呢?以ReentrantLock为例,lock()方法的实现。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
这里的acquire(1)的实现是在AQS中。
public final void acquire(int arg) {
if (!tryAcquire(arg) && //当前线程正在获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //在AQS队列中的线程尝试获取锁
selfInterrupt();
}
而tryAcquire的实现又在具体的实现类中实现了,同样以ReentrantLock中非公平锁为例。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //获取当前锁的状态位
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;
}
获取锁资源是通过AQS中的tryAcquire方法获取,根据不同需求在实现类中做相应的实现,如ReentrantLock中有公平锁和非公平锁,对于tryAcquire的实现就有所不同。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
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;
}
从代码中可以看到,不同之处在于公平锁多了!hasQueuedPredecessors()则个判断,这个方法是在判断是否有线程等待的时间比当前线程更长。
锁的获取分析到这里,锁的释放同样从ReentrantLock的unlock()方法开始,unlock方法很简单,直接调用release()方法(在ReentrantLock内部有内部类Sync继承AQS, ReentrantLock关于锁的操作都是通过Sync类进行的)。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
代码中的tryRelease调用的是AQS,而具体实现又在子类中,在ReentrantLock中。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//state状态值减一
if (Thread.currentThread() != getExclusiveOwnerThread()) //判断释放锁资源的是不是当前线程
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //当state为0时锁资源被释放了
free = true;
setExclusiveOwnerThread(null); //将持有锁资源的线程置为空
}
setState(c);
return free;
}
锁的获取和释放到这里大致可以了解了,在这里能过发现在AQS中不能后获取资源的线程都被放在了AQS的队列中了,这里线程将在何时去获取锁资源呢?
在AQS内部结构分析中有一个条件变量ConditionObject内部类,这个内部类可以访问AQS内部变量,从而可以控制线程之间的通信,像Synchronized锁定后,通过notify或者notifyAll唤醒其他线程获取所资源。
在ReentrantLock实例中可以创建多个ConditionObject,在AQS中会给每一个ConditionObject创建一个单独的队列,当线程调用条件变量的await() 方法时(必须先调用锁的lock() 方法获取锁),在内部会构造一个类型为Node.CONDITION 的node 节点,然后将该节点插入条件队列末尾,之后当前线程会释放获取的锁( 也就是会操作锁对应的state 变量的值),并被阻塞挂起。这时候如果有其他线程调用lock.lock() 尝试获取锁,就会有一个线程获取到锁,如果获取到锁的线程调用了条件变量的await ()方法,则该线程也会被放入条件变量的阻塞队列,然后释放获取到的锁,在await()方法处阻塞。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
当另外一个线程调用条件变量的signal方法时( 必须先调用锁的lock()方法获取锁) ,在内部会把条件队列里面队头的一个线程节点从条件队列里面移除并放入AQS 的阻塞队列里面, 然后激活这个线程。
到这里AQS队列中的线程和ConditionObject队列中的线程是如何通信的,在何时取重新获取锁资源的都已经清楚了。