AQS 系列:
1.共享-加锁
共享锁和排他锁最大的不同在于:对于同一个共享资源,排他锁只能让一个线程获得,而共享锁还会去唤醒自己的后续节点,一起来获得该锁
acquireShared()
共享模式下,尝试获得锁
// 共享锁可以让多个线程获得,arg 可以被子类当做任意参数,比如当做可获得锁线程的最大个数
public final void acquireShared(int arg) {
// tryAcquireShared 首先尝试获得锁,返回值小于 0 表示没有获得锁
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
下面是 ReentrantReadWriteLock 的 tryAcquireShared 方法。
注:这里不用 Reentrantlock 是因为 Reentrantlock 采用的是独占锁模式,没有 tryAcquireShared方法。
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);
}
doAcquireShared()
管理同步队列(拿锁+休眠),大部分逻辑和独占锁 acquireQueued 一致的
private void doAcquireShared(int arg) {
// node 追加到同步队列的队尾,acquireQueued 是排他模式
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 拿到 node 前一个节点
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
// 大于 0 说明成功获得锁
if (r >= 0) {
// 此处和排它锁也不同,排它锁使用的是 setHead,这里的 setHeadAndPropagate 方法
// 不仅仅是把当前节点设置成 head,还会唤醒头节点的后续节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 这里都和排他锁是一致的
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate()
- 把当前节点设置成头节点
- 看看后续节点有无正在等待,并且也是共享模式的,有的话唤醒这些节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 当前节点设置成头节点
setHead(node);
// propagate > 0 表示已经有节点获得共享锁了
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 共享模式,还唤醒头节点的后置节点
if (s == null || s.isShared())
doReleaseShared();
}
}
doReleaseShared(核心方法)
释放后置共享节点
private void doReleaseShared() {
// 自旋,保证所有线程正常的线程都能被唤醒
for (;;) {
Node h = head;
// 还没有到队尾,此时队列中至少有两个节点
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果头结点状态是 SIGNAL ,说明后续节点都需要唤醒
if (ws == Node.SIGNAL) {
// CAS 保证只有一个节点可以运行唤醒的操作
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 进行唤醒操作
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 退出自旋条件 h==head,一般出现于以下两种情况
// 第一种情况,头节点没有发生移动,结束。
// 第二种情况,因为此方法可以被两处调用,一次是获得锁的地方,一处是释放锁的地方,
// 加上共享锁的特性就是可以多个线程获得锁,也可以释放锁,这就导致头节点可能会发生变化,
// 如果头节点发生了变化,就继续循环,一直循环到头节点不变化时,结束循环。
if (h == head) // loop if head changed
break;
}
}
2.共享-释放
releaseShared()
共享模式下,释放当前线程的共享锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 这个方法就是线程在获得锁时,唤醒后续节点时调用的方法
doReleaseShared();
return true;
}
return false;
}
注:排它锁和共享锁主要体现在加锁时,多个线程能否获得同一个锁。但在锁释放时,是没有排它锁和共享锁的概念和策略的,概念仅仅针对锁获取。