AbstractQueuedSynchronizer
基础的同步器,主要实现了对等待队列的管理(入队出队,独占/共享模式下对等待节点的唤醒等),而具体的获取、释放的判断逻辑由子类实现
等待队列
节点类是AbstractQueuedSynchronizer一个内部类Node
该队列是一个双向队列,AQS的head变量指向头结点(不是等待节点,head.next才是队列中第一个等待节点),tail变量指向尾节点(等待节点)
而入队、出队操作,即为对tail和head的更新操作均为通过Unsafe的CAS操作进行更新
队列是延迟初始化,在加入节点的时候才进行初始化
acquire、release
AQS中提供了state属性(int)作为用于同步的状态
具体判断能否获取、释放的逻辑方式(基于state属性)都是abstract(如
tryAcquire,tryAcquireShared,tryRelease,
tryReleaseShared),交由子类实现
acquire
获取释放分为两个模式:
- 独占模式
- 共享模式:在acquire获取成功后,如果下一个等待节点是共享模式的,则会唤醒该节点(正是该特性,让共享模式下,能同时获取成功)
都是通过CAS的方式保证并发安全(已进入等待队列的节点,按照先进先出的规则执行)。
获取的流程:
- 调用try方法尝试获取,获取成功直接返回,否则进入步骤
- 以指定模式(独占 or 共享)创建节点并添加到AQS队列中
- 判断当前节点是否为第一个等待节点,如果不是进入步骤4,如果是则把head指向该节点(setHead方法,相当于该节点已经出队了,共享模式此时会把后续共享模式的节点唤醒),然后返回interrupted(默认为false,会在步骤5中被修改),进入到步骤6
- 调用shouldParkAfterFailedAcquire,判断前面前面的等待节点是否有效,如果前面的节点已被取消先清理前面的节点再到步骤3,如果前面的节点状态不是等待唤醒则更新状态再到步骤3,如果前面的节点正常则挂起当前线程,到步骤5
- 如果步骤4中挂起了线程,从挂起中恢复,判断是被中断还是通过unpark导致的唤醒(parkAndCheckInterrupt),如果是中断导致的把interrupted置为true(会影响到后续是否挂起线程),进入步骤3
- 如果interrupted为true(表明在等待的时候,被别的线程中断了),挂起该线程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
值得注意的是,步骤5中为什么从中断恢复后,还要判断被唤醒还是中断导致的恢复?开始看的时候看得很迷茫,为什么要这么判断,后面看了LockSupport.park方法,里面注释提到了,对该线程的LockSupport.unpark或者Thread.interrupt方法都会导致线程中挂起中恢复
release
释放的流程:
- 调用try方法尝试释放,释放成功进入下一步骤,否则直接返回
- 把head节点状态置为0,从等待队列中取出第一个未被取消的等待节点,并唤醒该节点
独占模式的释放代码:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
基于AQS的同步类
目前java中基于AQS的有以下几个:
- ReentrantLock
- ReentrantReadWriteLock
- Semaphore
- CountDownLatch
- ThreadPoolExecutor
Lock:ReentrantLock
ReentrantLock在构建的时候,可以以两种模式创建,主要是在tryAcquire中是否需要判断前面有没有等待者:
- 公平模式:先进先出,先等待的会先去到锁 非公平模式:不保证先等待的会先得到锁,锁刚释放并此时有获取锁的线程会先得到锁,减少了线程唤醒的开销
公平模式的tryAcquire:
- 判断state是否为0,如果是并且是第一个等待者,则获取成
- 如果不是第一个等待者,再判断该线程是否与拥有锁的线程为同一线程,如果是则state属性加1(acquire),获取成功(可重入锁)
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,如果是则直接获取锁成功
- 与公平模式相同
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
Lock:ReentrantReadWriteLock
ReentrantReadWriteLock也可以用公平、非公平两种模式创建
同一个线程可同时拥有读、写锁
总结:
获取读锁时与写锁关系:(回顾下上面AQS的acquire中,共享模式下获取成功,只要下一个也是共享模式的节点,会继续唤醒该节点)
- 当前有本线程的写锁,获取成功
- 当前有非本线程的写锁,获取失败,添加到AQS等待队列
- 公平模式下,当前无写锁,前面有等待者,进入等待队列,没有等待者则获取成功
- 非公平模式下,如果当前第一个等待者在等待写锁,则进入等待队列(直到该节点前面所有的写节点执行完成,就轮到该节点执行),否则获取成功
获取写锁时与读锁关系:
- 当前有读锁,添加到AQS等待队列
- 当前有写锁,如果不是本线程的,添加到AQS等待队列;如果是本线程的,获取成功
- 当前没有读锁、写锁,获取成功
ReentrantReadWriteLock包含了两个锁:读锁和写锁,并且是用的同一个同步器,从上面AQS可知,AQS内部只有一个state属性来表示锁,因此这里把int型的state分成两半使用,前16位用来控制写锁的数量,后16位作为读锁的数量
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
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; }
写锁
写锁是独占锁,
获取流程:
- 判断当前是否有读写线程,如果有进入步骤2,没有则到步骤4
- 如果有读线程或者有非自己的写线程,返回false,否则进入步骤3
- 如果已经达到最后数量,抛出异常,否则更新state状态,返回ture(获取成功)
- 如果是公平模式,需要判断前面是否还有等待写的线程,有则返回false,没有则更新状态、更新当前独占线程,返回true
protected final boolean tryAcquire(int acquires) {
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)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
释放流程比较简单,判断是否为拥有者,然后更新状态、独占线程即可
读锁
从下面流程基本可以看到,让读线程堵塞等待的原因有三个
- 有非当前线程的写线程
- 读线程数已满(65535)
- 公平模式下,前面有等待者;非公平模式下,第一个等待者为写线程
readHolds是ThreadLocal类型,用于记录该线程获取了锁的数量
获取流程:
- 判断当前是否有非自己的写线程,如果有则直接返回false,没有则到步骤2
- 如果是公平模式,则判断前面是否有等待者;如果是非公平模式,则判断当前第一个等待者是否为写线程(如果等一个等待者是读线程,则代表可以直接获取读锁,不用等待,因为是非公平);如果满足条件,并且没有超过读的最大数量,则进入步骤3,不满足进入步骤4
- 更新state的值,成功后根据情况更新firstReader 、firstReaderHoldCount、cachedHoldCounter(最后一次获取成功读线程信息),返回1(获取成功)
- 调用fullTryAcquireShared方法重试(重新跑了一遍1-3的不步骤),结束后退出
对步骤4还是比较疑惑,他差不多就是1-3步骤的重复,根据他的解释说也是一个重试步骤,但是为什么需要这个重试不大懂
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
释放流程,基本就是根据情况更新firstReader、firstReaderHoldCount信息,并且根据readHolds中判断本线程是否已经不再拥有任何读锁,如果没有了,从readHolds中去掉,最后更新state信息
Semaphore
整体逻辑比上面对的可重入锁和读写锁都简单了很多,在初始化的时候,可以指定state的值,即可以被无释放的acquire多少次,并且信号量不是可重入的,因此也不用判断是否该线程已获取
与读写锁中的读锁一样,是以共享模式进行获取释放
信号量也支持指定公平\非公平模式,因此在公平模式下还需要判断是否有前面的等待者
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
CountDownLatch
这个跟上面3有点不一样,就是上面3类似于通行证,获取了才能执行,而这边是释放了才能执行
创建的时候,会把state初始化一个指定值,await相当于等待state变为0,而countDown就是给state减1
而还有个注意的是await提供了两个版本:
- public void await() throws InterruptedException:等待过程中被中断了,会抛出异常
- public boolean await(long timeout, TimeUnit unit) throws InterruptedException:在上面的基础上,增加了可以指定等待的时长
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
public void countDown() {
sync.releaseShared(1);
}
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;
}
}
ThreadPoolExecutor
线程池中的Worker继承于AbstractQueuedSynchronizer,是一个不可重入锁
而为什么Worker要用到AQS?
ThreadPoolExecutor在执行shutdown命令的时候,这个命令是会允许当前正在执行的任务完成,因此通过AQS来防止竞争,不可重入也是为了防止此时tryLock成功
Worker的tryAcquyre和tryRelease方法:
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
shutdown:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}