AQS的实现基于如下三个
- volatile,保证了可见性与防止指令重排,正常加锁都是需要修改state的值,volatile写不会和前面操作重排序,第二个线程也是volatile写,两个volatile写之间不会重排序,这样第一个线程所有操作第二个线程都能感知到
- CAS 原子的更新state值,如果能成功更新,可以表示加锁成功
- LockSupport, 让线程暂停与恢复
LockSupport
先看下LockSupport中park,unpark以及线程中断的伪代码
park(){
if(permit==1){
permit=0;
return;
}
if(thread.isInterrupted() == true){
return;
}
阻塞线程
permit=0;
}
unpark(){
if(permit<1){
permit=1;
if(线程被阻塞){
唤醒线程
}
}
}
interrupt(){
if(中断状态==false){
中断状态=true;
}
unpark()
}
LockSupport支持先执行unpark,再执行park这样也是不会阻塞的,park会去判断是否permit=1,是的话直接返回.
以前只以为中断操作只是给中断变量做一个赋值操作,实际上会去做唤醒线程的操作.
AQS源码
数据结构
AQS主要的数据结构是一个CLH链表,链表中结构如下
volatile Node prev; // initially attached via casTail
volatile Node next; // visibly nonnull when signallable
Thread waiter; // visibly nonnull when enqueued
volatile int status; // written by owner, atomic bit ops by others
waiter记录的是当前线程,status记录当前node节点的状态.
CLH队列有head和tail两个节点,tail节点就是正常加入到队列中的节点,head节点其实也是正常加入到队列中的节点,只是就算CLH队列为空,也会存在一个head节点,所以实际上一个节点判断出它的上一个节点就是head节点,就可以认为它已经在队首了,可以尝试调用tryAcquire获取锁.
state变量
AQS中提供了一个volatile int state变量,初始时候state=0,所有继承AQS的类通过state变量来实现自己的需求,判断是否是加锁状态之类的,比如reentrantlock中state=0表示未加锁状态,state>0表示加锁状态,且由于是可重入锁,state=x表示当前线程重入锁x次. 其他像countdownLatch等如何利用state后面遇到了再讲.
AQS提供的操纵state方法中只有get,set,以及compareAndSetState,通过compareAndSetState就能实现原子的比较state原始值并赋予新值操作,像是reentrantlock就是通过此方法来修改state的值,如果能成功的将state由0改成1,证明获取锁成功.
acquire方法
AQS最重要的就是acquire方法了,下面详细讲解下
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0; // retries upon unpark of first thread
boolean interrupted = false, first = false;
Node pred = null; // predecessor of node when enqueued
//上面几个字段先解释下
//spins是在当前节点已经是head节点下一个我们称为头结点时候,由于非公平锁,所以即使是头节点
//也不能保证一定能获取到锁,当未获取到锁时,循环spins次数尝试获取锁,未获取到才会暂停线程
//postSpins,配合spins记录上一次暂停线程spins次数,此次线程被唤醒后spins次数为
//postSpins*2+1
//first表示当前node是否为头结点
//pred表示当前节点的上一节点
/*
* Repeatedly:
* Check if node now first
* if so, ensure head stable, else ensure valid predecessor
* if node is first or not yet enqueued, try acquiring
* else if queue is not initialized, do so by attaching new header node
* resort to spinwait on OOME trying to create node
* else if node not yet created, create it
* resort to spinwait on OOME trying to create node
* else if not yet enqueued, try once to enqueue
* else if woken from park, retry (up to postSpins times)
* else if WAITING status not set, set and retry
* else park and clear WAITING status, and check cancellation
*/
for (;;) {
// 判断当前节点不是头节点且当前节点加入到CLH队列中
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
//前驱节点被取消了,从队列中移除前驱节点
if (pred.status < 0) {
//将队列中状态为cancel的节点移除出队列
cleanQueue(); // predecessor cancelled
continue;
} else if (pred.prev == null) {
Thread.onSpinWait(); // ensure serialization
continue;
}
}
// 当前节点是头结点 | 当前节点还未创建 | 当前节点还未加入到CLH队列中
// 这种情况可以尝试获取锁
if (first || pred == null) {
boolean acquired;
try {
if (shared)
tryAcquireShared方法是由上层继承AQS的类去实现
acquired = (tryAcquireShared(arg) >= 0);
else
//tryAcquire方法是由上层继承AQS的类去实现
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
//share模式的话判断node下一个节点是否是share节点,是的话唤醒下一个节点
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
}
Node t;
//tail还不存在表明CLH队列从未创建过,初始化一个节点,head和tail都指向它
if ((t = tail) == null) { // initialize queue
if (tryInitializeHead() == null)
return acquireOnOOME(shared, arg);
//当前节点为空的话创建当前节点
} else if (node == null) { // allocate; retry before enqueue
try {
node = (shared) ? new SharedNode() : new ExclusiveNode();
} catch (OutOfMemoryError oome) {
return acquireOnOOME(shared, arg);
}
//当前节点加入CLH队列
} else if (pred == null) { // try to enqueue
node.waiter = current;
node.setPrevRelaxed(t); // avoid unnecessary fence
if (!casTail(t, node))
node.setPrevRelaxed(null); // back out
else
t.next = node;
// 当是头结点且spins自旋次数>0时,只睡眠很短时间,又进入上面tryAcquire逻辑
} else if (first && spins != 0) {
--spins; // reduce unfairness on rewaits
Thread.onSpinWait();
//更改当前node状态为waiting,并再次回到循环头进行检查
} else if (node.status == 0) {
node.status = WAITING; // enable signal and recheck
} else {
long nanos;
// 指定当是头结点时自旋次数为上一次park前自旋次数*2+1,初始为1,
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)
LockSupport.park(this);
//指定获取锁等待时间的当到达时间后,下面逻辑会清除状态,如果已经是头结点,会去尝试
//获取锁,不是头结点重新设置状态waiting,再去判断是否是头结点,当还不是时才会取消
//获取锁
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
//如果当前线程被中断过且acquire方法支持中断的话退出循环
if ((interrupted |= Thread.interrupted()) && interruptible)
break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}
流程如下,先翻一下acquire方法中的注释
循环
* Repeatedly:
检查node是否是头结点
* Check if node now first
是的话,确保head节点稳定
不是的话,确保前驱是有效的,即执行cleanQueue操作,删除队列中cancel的节点
* if so, ensure head stable, else ensure valid predecessor
如果node是头结点或者还未入队,尝试获取锁
* if node is first or not yet enqueued, try acquiring
如果队列还未初始化,初始化节点并赋值给head和tail
* else if queue is not initialized, do so by attaching new header node
* resort to spinwait on OOME trying to create node
如果当前node未创建,创建它
* else if node not yet created, create it
* resort to spinwait on OOME trying to create node
* else if not yet enqueued, try once to enqueue
* else if woken from park, retry (up to postSpins times)
* else if WAITING status not set, set and retry
* else park and clear WAITING status, and check cancellation
AQS流程
- 未加入队列或者是队列中头结点,执行tryAcquire或者tryAcquireShared
- 成功tryAcquireShared的话,判断下一个节点是否是shared节点以及状态是waiting,是的话执行unpark唤醒,这里是链式唤醒,当前节点只会唤醒下一个节点,写一个节点如果成功tryAcquireShared,下一个节点会去唤醒下下个节点
- 判断当前node是否为null,是的话初始化
- 判断当前node是否加入队列,未加入的话加入
- 判断当前node status==0,是的话设置status=waiting
- 执行park,增加下次自旋次数spins,park醒来后检查中断状态是否应该退出循环
node status状态说明
node状态有四个,初始化node节点为0,waiting=1,cond=2,CANCELLED = 0x80000000为一个负数.
cond和cancelled状态非常好理解,主要讲下初始化状态以及waiting状态.
waiting状态表示当前节点处于一个可以被其他线程执行unpark唤醒的状态,可以看下signalNext函数
private static void signalNext(Node h) {
Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}
head节点在唤醒下一个节点时候会去判断状态是否为0,不为0的才会唤醒. 为0时候下一节点还在acquire循环中,至少还有一次循环会去判断是否是头结点并执行tryAcquire,此时下一节点并没有park,所以这里不需要unpark.
AQS应用
ReentrantLock
final void lock() {
if (!initialTryLock())
acquire(1);
}
//公平锁的加锁逻辑
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//判断CLH队列中是否还有其他线程在等待,没有的话尝试将state由0改为1
//公平锁和非公平锁唯一区别就是是否需要判断hasQueuedThreads,非公平锁上来就是尝试
//cas将state由0改为1,不去判断队列中是否已经有线程在等待.
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
//AQS中设置exclusiveOwnerThread变量为当前线程
setExclusiveOwnerThread(current);
return true;
}
//如果AQS中exclusiveOwnerThread变量代表的线程和当前线程一致,直接修改state变量即可,
//reentrantLock本身就是可重入锁,支持这样多次加锁
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
ReentrantReadWriteLock
先看下在读写锁中state是如何使用的,毕竟要保存读锁和写锁状态
AQS中state是int类型32位的,读写锁中使用高16位保存读锁状态,低16位保存写锁状态
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count. */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count. */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
下面是读锁的逻辑,AQS acquire方法已经实现了整体逻辑了,我们只需要按需实现tryAcquire或者tryAcquireShared方法即可, 这里读锁多个线程都可进入,所以实现的是tryAcquireShared
//加了读锁的线程都会有,记录当前线程加了几次读锁,避免加了两次锁,释放了三次情况
//holderCounter保存在threadLocal中
static final class HoldCounter {
int count; // initially 0
// Use id, not reference, to avoid garbage retention
final long tid = LockSupport.getThreadId(Thread.currentThread());
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
// AQS的state是int类型,在读写锁中将state高16位
Thread current = Thread.currentThread();
int c = getState();
//未加写锁,或者加了写锁但是写锁是当前线程加的
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
// cas成功给state高16位读锁+1代表加读锁成功,成功后给自己线程的holdCounter+1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
接下来看下写锁tryAcquire方法
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//c不为空表示一定是加了读锁或者写锁的,写锁=0,表示加了读锁,或者写锁!=0,但是加锁的线程
//不是当前线程,这种情况加锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//这里当前线程已经获取写锁了,这里是重入,不需要cas去设置state
setState(c + acquires);
return true;
}
//state=0表示没有加读锁或者写锁,cas尝试加锁
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
CountDownLatch
经常忘记countDownLatch的用法, 它可以让一个线程等待,直至所有其他线程完成各自工作. 它提供了两个方法,countDown和await,工作线程完成后调用countDown,总的线程调用await去等待工作线程完成.
public void countDown() {
sync.releaseShared(1);
}
//countDown就是做一个释放锁的操作
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
//await是获取共享锁的操作,意味着可以有多个线程都在await,当countDown将state减到0时,就会唤醒
//所有await的线程
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
CycliBarrier
CycliBarrier用法也总是想不起来,还是不太常用加上不熟悉原理缘故. CyCliBarrier可以翻译为循环栅栏,即当所有线程都到达栅栏时候,同时再往后执行.CyCliBarrier可以指定一个数字N,N个线程执行await等待其他线程到达,当最后一个线程执行await时,所有线程取消park,继续往后执行.
这里就不贴代码了,讲下原理.
CyCliBarrier使用了reentrantlock以及一个condition来实现,有一个变量count,当线程执行await时候,lock加锁,count-1并判断是否为0,即是否是最后到达线程,非最后一个的话,执行condition.await,最后一个线程的话执行condition.signalAll,
AQS条件队列
总结下条件队列,node加入到条件队列不用考虑多线程竞争的情况,await调用enableWait再调用isHeldExclusively,这个方法需要继承AQS的去实现,目前只有reentrantlock,reentrantReadWriteLock实现了,都是去判断AQS exclusiveOwnerThread变量是否为当前线程,这样相当于只有一个变量可以执行await和signal操作,就不需要考虑多线程竞争情况.
condition的使用需要在lock范围内,即condition的await与signal操作需要再lock.lock以及lock.unlock范围内.
每次new Condition其实是new了一个ConditionObject,ConditionObject包含firstWaiter,lastWaiter,所以ConditionObject其实就是个队列,线程可以park在不同的ConditionObject上
执行condition.signal不会实际去唤醒下一个node节点,只是把下一个node节点加入到CLH队列,因为执行condition.signal需要在锁里面,当前线程最后会执行lock.unlock这种操作,实际上会去执行aqs release操作,里面会去执行aqs signalNext,才会去实际唤醒下一个节点.
private void doSignal(ConditionNode first, boolean all) {
while (first != null) {
ConditionNode next = first.nextWaiter;
//下一个节点变成firstWaiter
if ((firstWaiter = next) == null)
lastWaiter = null;
//first节点状态包含cond
if ((first.getAndUnsetStatus(COND) & COND) != 0) {
//加入到CLH队列
enqueue(first);
if (!all)
break;
}
first = next;
}
}
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
ConditionNode node = newConditionNode();
if (node == null)
return;
//当前线程执行release释放状态,唤醒后继节点,设置node状态为cond|wait
int savedState = enableWait(node);
LockSupport.setCurrentBlocker(this); // for back-compatibility
boolean interrupted = false, cancelled = false, rejected = false;
//检查node是否在CLH队列中,不在队列中进入下面循环
while (!canReacquire(node)) {
//等价于if(interrupted=interrupted|hread.interrupted())
//如果线程中断过就进入这个if
if (interrupted |= Thread.interrupted()) {
//第一次进这个if时,node状态为cond|wait,get时候返回为3,3&2!=0
//即中断后会退出while循环
if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
break; // else interrupted after signal
}
//判断node状态是否包含cond状态,包含的话执行park操作
else if ((node.status & COND) != 0) {
try {
if (rejected)
node.block();
else
ForkJoinPool.managedBlock(node);
} catch (RejectedExecutionException ex) {
rejected = true;
} catch (InterruptedException ie) {
interrupted = true;
}
} else
Thread.onSpinWait(); // awoke while enqueuing
}
//退出循环有两种情况,一种是中断退出,一种是其他线程执行了condition.signal将当前node加入到CLH队列,
//node此时还是park的,直到被CLH队列头结点执行release时候方法内的signalNext唤醒,才会继续执行下面代码
LockSupport.setCurrentBlocker(null);
node.clearStatus();
//不管是中断执行到这的还是正常被condition.signal后才到这的,都需要重新获取锁
acquire(node, savedState, false, false, false, 0L);
if (interrupted) {
if (cancelled) {
unlinkCancelledWaiters(node);
throw new InterruptedException();
}
Thread.currentThread().interrupt();
}
}
BlockingQueue
ArrayBlockingQueue
ArrayBlockQueue和我们自己实现的生产者消费者差不多,底层都是一个数组,一个reentrantlock,根据reentranlock生成的emptyCondition,fullCondition.
放入数据的时候如果满了,就调用fullCondition.await,让放数据线程进入等待,否则放入数据并调用emptyCondition.signal.
消费数据和放数据同理,数组为空时候,调用emptyCondition.await,否则正常消费数据并调用fullCondition,signal
LinkedBlockingQueue
之前以为linkedBlockingQueue的实现会和arrayBlockingQueue一样,只是一个底层是数据,一个是链表而已,结果并不是. 下面看下LinkedBlockingQueue的实现.
LinkedBlockingQueue使用了两个reentrantLock,taekLock和putLock,takeLock有一个notEmptyCondition,putLock有一个notFullCondition. 同时还有一个AtomicInteger类型的count,记录当前queue里面数量.
放入逻辑如下
放入数据的时候使用putLock加锁,满了就调用notFullCondition.await,否则正常放入.
如果放入后还有容量的话,调用notFullCondition.signal.这里和arrayBlockQueue不一样,array是由take操作调用notFullCondition.signal,这里是由put操作调用.
放入前如果count=0,调用notEmpty.signal,count=0表明可能有take操作被阻塞了需要被唤醒.
take数据逻辑就不描述了,和放入是一样的.
Array与Linked原理不同点
Array的实现put,take用的都是同一把锁,Linked用了两把锁,put,take不会互相阻塞,理论上Array也是可以用两把锁来实现的.自己实现一个. 见com.freedom.thread.aqs.MyArrayBlockingQueue