深入理解AQS - ReadWriteLock实现

ReadWriteLock维护了一对相关的锁,分别是只读锁、写锁。只读锁可以被多个线程同时持有,写锁是独占的。

ReentrantReadWriteLock实现了ReadWriteLock接口,支持类似ReentrantLock功能。读写锁会更加体现公平性问题,比如读锁和写锁的获取顺序。实现类中也提供了两种模式,公平和非公平模式,默认为非公平的。

接下来我们看下相关源码,来分析下ReentrantReadWriteLock的实现原理,以及提供的两种方式的区别。

我们首先看下写锁的获取,下面几个公共方法是作为判定当前持有锁的类型:

1
2
3
4
5
6
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;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

获取写锁流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

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;
}

// 非公平模式下
final boolean writerShouldBlock() {
return false; // writers can always barge
}

// 公平模式下
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}

public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

写锁的获取流程同ReentrantLock获取锁相同,子类通过实现tryAcquire来实现具体的流程,我们看下代码:

如果当前没有线程获取锁,也就是state = 0,此时会直接通过CAS设置state的值,如果成功则返回,获取到了锁。如果当前其他线程获取到锁,

  • 持有的是读锁 w = 0,则会调用acquireQueued方法,上节我们分析过了会进入队列并阻塞。
  • 持有的事写锁,当前线程非独占线程,同样也是调用acquireQueued方法。
  • 独占为当前线程,表示重入该锁。
  • 获取锁的数量是有上限的,也是就是上面MAX_COUNT,如果请求锁的数量大于该值,则会抛出异常

我们先看下一个线程是如何获取读锁的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果被其他线程持有写锁
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 1
return -1;
int r = sharedCount(c); // 2
if (!readerShouldBlock() /*1*/ && r < MAX_COUNT /*2*/ && compareAndSetState(c, c + SHARED_UNIT) /*3*/) { // 3
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); // 4
}

final boolean apparentlyFirstQueuedIsExclusive() { // 5
Node h, s;
return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null;
}

当获取读锁时,如果其他线程持有写锁,并且不是当前线程,则直接进入doAcquireShared流程,会在1处返回。

在持有写锁的情况下,支持锁的降级,同一个线程获取写锁的情况下,可以再次获取读锁,来实现降级。进入3内的代码。

同时多个线程获取读锁是不需要阻塞的,可以同时获取读锁。不过在竞争条件下获取读锁,会进入步骤4来获取读锁。

我们根据上面在获取写锁的情况下,如果不是当前线程持有锁,则会进入doAcquireShared,会进入队列,并且阻塞当前线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
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);
}
}

当写锁调用unlock释放后,后续等待的读锁的线程会依次释放,直到下一个获取写锁Node。

坚持原创技术分享,更多深度分析、实践代码,您的支持将鼓励我继续创作!