什么是AQS
AQS,全称抽象队列同步器(AbstractQueuedSynchronizer),顾名思义是一种能够实现线程同步的队列。实际上,java.util.concurrent.locks包下的许多锁都依赖AQS组件。
例如,在ReentrantLock类中,定义了一个内部类Sync,来继承AbstractQueuedSynchronizer类
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
AQS的实现
AQS主要基于三个东西:volatile int state (状态量), ExclusiveOwnerThread (锁持有线程),还有一个 FIFO队列
状态量state类似于操作系统中的信号量,volatile关键字可以保证对所有线程可见。初始状态下,state的值为0。
加锁
当线程一调用lock() 方法时,会用到tryAcquire() 方法获取锁,ReentrantLock中的 nonfairTryAcquire() 方法进行了实现。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //获取state的值
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;
}
当state为0,说明没有变量持有锁,则通过CAS操作将state置1,加锁成功;否则,需要判断当前线程是不是持有锁的线程,是则加锁成功,将state + 1,否则则加锁失败。
ReentrantLock是可重入锁,即可以多次加锁。一个线程可以重入多次加锁,每次判断一下当前加锁线程是它自己,然后将state的值加1。
加锁失败
AQS维护了一个双向链表,每个节点都有 prev 和 next 两个指针,当前线程一是队列的队头,当线程二加锁失败后,它会添加到线程一的后面,同理,线程三加锁失败后,会添加到线程二的后面。
链表每个Node都有一个 head 指针和一个 tail 指针。会先判断当前传入的Node对应的前置节点是否为head,如果是则尝试加锁。加锁成功过则将当前节点设置为head节点,然后空置之前的head节点,方便后续被回收。
如果加锁失败或者Node的前置节点不是head节点,则将head节点的 waitStatus 变为了SIGNAL=-1,最后执行parkAndChecknIterrupt() 方法,调用LockSupport.park()挂起当前线程。
释放锁
当线程一释放锁之后,state被设置成0,ExclusiveOwnerThread被置为null,head节点的waitStatus置为0,然后解除head节点的next指针,使head节点空置,等待被回收。此时重新将head指向线程二,并使用 LockSupport.unpark()方法来唤醒线程二。被唤醒的线程二会接着尝试获取锁,用CAS指令修改state数据。
公平锁与非公平锁
非公平锁就是,当线程一释放锁后,唤醒线程二,这时候如果进来一个线程四,它同样可以调用tryAcquire() 方法抢占锁,这就出现一个情况,线程二等了大半天,结果被线程四抢到了锁,ReentrantLock默认是非公平锁。
ReentrantLock.FairSync实现了公平锁的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;
}
当state为0,会先调用hasQueuedPredecessors() 方法判断队列中是否还有等待线程,如果有,则将当前线程加入等待,做到先来后到,顺序加锁。
非公平锁性能高于公平锁,但是可能造成线程饥饿的情况,即某一个线程一直拿不到锁。