从源码中获得的体会
读锁的获取
/**
* tryAcquire
* 获得state的值,看是否为0, 再获得w的值,也就是低16位写锁的值
* state = 0,那么直接尝试去加锁,替换state,如果替换失败进队列,如果超出16位数的限制范围抛异常
* state不为0,就看一下是不是当前有写锁不是这个线程或者有读锁,有就加锁失败进队列,再在同步队列里尝试去获得锁
*/
lock.writeLock().lock();
/**
* 读锁,读锁本身是可重入的共享锁,可以被多个线程同时获取
* 读锁的获取有三个判断条件:
* 1. 不能被阻塞,也就是head队列后头不能有独占的节点或者公平锁就是后面不能有节点在等待
* 2. 不能超出高16位的表示数限制
* 3. CAS获取锁成功
* 但是失败之后还会给它一次机会去fulltry一下,如果不是因为阻塞,等待队列里没东西,那么就可以继续CAS去获取!!!
* 1. 如果其他线程有写锁并且不是自己,那么就直接退出返回失败
* 2. 如果被阻塞了,这时候要判断一下重入了,threadlocal里面存的值就有用了,如果等于0说明不会发生重入,那么返回失败,不等于0就处理一下重入
* 3. 有没有爆出高16位的表达范围,如果有那么退出
* 4. 死循环CAS
* 这其实也说明了,写锁会有更更高的优先级
* 每个线程各自获取读锁的次数只能选择保存在本地的threadLocal里面
* A充入3次,B重入1次,A的ThreadLocal保存了3,B的ThreadLocal保存了1 而读状态是3 + 1
* tryAcquireShared
*
*/
lock.readLock().lock();
/**
* 读锁,读锁本身是可重入的共享锁,可以被多个线程同时获取
* 读锁的获取有三个判断条件:
* 1. 不能被阻塞,也就是head队列后头不能有独占的节点或者公平锁就是后面不能有节点在等待
* 2. 不能超出高16位的表示数限制
* 3. CAS获取锁成功
* 但是失败之后还会给它一次机会去fulltry一下,如果不是因为阻塞,等待队列里没东西,那么就可以继续CAS去获取!!!
* 1. 如果其他线程有写锁并且不是自己,那么就直接退出返回失败
* 2. 如果被阻塞了,这时候要判断一下重入了,threadlocal里面存的值就有用了,如果等于0说明不会发生重入,那么返回失败,不等于0就处理一下重入
* 3. 有没有爆出高16位的表达范围,如果有那么退出
* 4. 死循环CAS
* 这其实也说明了,写锁会有更更高的优先级
读锁的释放
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
if (firstReaderHoldCount == 1)
// 如果等于 1,那么这次解锁后就不再持有锁了,把 firstReader 置为 null,给后来的线程用
// 为什么不顺便设置 firstReaderHoldCount = 0?因为没必要,其他线程使用的时候自己会设值
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 判断 cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
// 这一步将 ThreadLocal remove 掉,防止内存泄漏。因为已经不再持有读锁了
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
// count 减 1
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
//这里设置为0其实就是说明后面的节点可以来获得锁了,帮助写锁和读锁的获取
return nextc == 0;
}
}
写锁
写锁由于是独占的,和ReentrantLock比较相似,就不额外列出。
有个比较有意思的是writeShouldBlock方法,它在这的作用其实就是区分公平锁和非公平锁吧。