AQS是J.U.C的基础,J.U.C中的很多并发工具,例如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS来实现。
AQS中维护了一个volatile的整型变量state,和一个FIFO的同步队列(使用双向链表实现)。线程通过对state进行CAS操作来获取锁,获取锁失败后会被加入到同步队列中,调用park进行挂起,等待锁被释放后被其他线程唤醒。
AQS双向队列中的元素为Node,Node的结构定义如下:
static final class Node {
static final int CANCELLED =1; //表示线程获取锁的请求已经取消了
static final int SIGNAL = -1; //表示线程已经准备好了,就等资源释放了
static final int CONDITION = -2;//表示节点在等待队列中,节点线程等待唤醒
static final int PROPAGATE = -3; //当前线程处在SHARED情况下,该字段才会使用
volatile int waitStatus; //当前节点在队列中的状态,上面的几个常量int值为waitStatus的几个状态
volatile Node prev; //前驱指针
volatile Node next;//后继指针
volatile Thread thread; //表示处于该节点的线程
Node nextWaiter;//指向下一个处于CONDITION状态的节点
}
非公平锁加锁过程:
ReentrantLock.lock() -> NonfairSync.lock() :
1、 对state变量进行cas操作争抢锁,cas成功,setExclusiveOwnerThread为当前线程,取锁成功
2、cas失败,执行AQS的acquire方法
① nonfairSync.tryAcquire():如果state等于0,再次进行cas操作,成功则获取锁,返回true;如果state不等于0,检查当前线程是否为线程,如果为本线程,state自增,返回true;否则返回false;
② 若①返回false,则调用addWaiter方法将本线程封装为Node节点进行入队操作:若队列没有元素,则新建一个虚节点作为Head节点,使用CAS将当前线程加入到队列的队尾。调用acquireQueue判断当前传入的Node对应的前置节点是否为head,如果是则尝试加锁。加锁成功过则将当前节点设置为head节点,然后空置之前的head节点,方便后续被垃圾回收掉。如果加锁失败或者Node的前置节点不是head节点,就会通过shouldParkAfterFailedAcquire方法将head节点的waitStatus变为了SIGNAL=-1,最后执行parkAndChecknIterrupt方法,调用LockSupport.park()挂起当前线程
非公平锁解锁过程:
ReentrantLock.unLock() -> AQS.release() :
1、调用nonfairSync的tryRelease()方法,将state进行自减,如果state==0,则setExclusiveOwnerThread为null,释放锁,return true;若state不为0,则return false;
2、若tryRelease返回true,则继续判断head节点的waitStatus是否为0,前面我们已经看到过,head的waitStatue为SIGNAL(-1),这里就会执行unparkSuccessor():将head节点的waitStatus设置为0,重新将head指针指向next节点,且使用LockSupport.unpark next节点。被唤醒的线程会接着尝试获取锁,用CAS指令修改state数据,接着之前被park的地方继续执行,继续执行acquireQueued()方法。
网友评论