怎么加写锁的?
因为写锁和读锁、写锁都冲突,所以如果state非0,那么说明加锁失败,返回false,把当前线程加入CAS队列,有读锁释放的时候唤醒。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); //状态,高位包存了读锁的数量,低16位保存了写锁的数量
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() || //监测是否需要等待,取值YES or NO,可以实现公平锁和非公平锁
!compareAndSetState(c, c + acquires)) // CAS改变state
return false;
setExclusiveOwnerThread(current); // CAS成功,标志加锁成功
return true;
}
可以看到tryAcquire里面实现了读写锁冲突的语义,以及公平锁和非公平锁的语义。
怎么加读锁的?
同样读锁和写锁冲突,如果有线程持有写锁,那么加锁失败。否则判断是否有写锁在等待,没有就去CAS竞争,否则有fullTryAcquireShared处理
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()&& //公平锁和非公平锁,公平锁:读锁和写锁之间有先后顺序,也就是有写锁在等待,那么读锁也需要排队。 //非公平锁:可以自由竞争,但是为了防止写锁出现饥渴,会返回false,导致读锁等待(因为写少并且写重要些嘛)
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 != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current); // 加锁失败,然后在for循环中反复尝试,直到成功或者遇到写锁
}
另外一个疑问是如果当前线程持有写锁,另外一个线程在等待写锁,那么当前线程尝试加读锁是怎么样的?从上面代码可以看到这种情况在fullTryAcquireShared中处理:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
return -1;
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
return 1;
}
}
}
仅保留了核心代码,可以看到在for循环中处理了两种情况:如果有写锁,那么是不是当前线程持有这个写锁,如果是那么CAS后加锁成功返回,不是就返回失败;如果没有写锁,那么判断是否有写锁在等待,如果有返回-1去CAS排队,如果没有,那么CAS竞争。加锁的代码已经分析完毕。
再来看释放锁的逻辑:
写锁:
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
仍然是修改状态,因为是写锁,所以不需要CAS,直接修改状态即可,如果free是空,那么会打醒后面等待的线程,如果非空,说明当前线程是重入的。不需要唤醒后继线程。
读锁:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// ...
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
只保留了核心代码,可以看到需要通过CAS来改变STATE的值,因为可以有多个线程同时持有读锁。重点看返回值,nextc如果为0,说明没有读锁,那么如果后继线程是writer的话,会唤醒来竞争锁。但是nextc如果非0,那么也应该唤醒后继的reader呀,其实唤醒reader的逻辑在acquire 读锁的时候就执行了,在tryAcquireshared中,如果加读锁成功,那么会返回1,这样就会唤醒等待的reader了,专门的术语叫Propagate(传播)。