ReadWriteLock 第二篇
提示:看了 ReadWriteLock 第一篇 才能看这一篇 ,关于ReadWriteLock 知识点明白上一篇讲的内容应付一般面试没什么问题了。
1. hasQueuedPredecessors
上一篇在获取读共享锁流程中有一个判断 ,
if (!readerShouldBlock() &&
如果readerShouldBlock返回false 那就正常获取锁,如果返回true那么就结束获取锁
这里说一下公平锁这个方法的内容:
// ReentrantReadWriteLock.FairSync#readerShouldBlock final boolean readerShouldBlock() { return hasQueuedPredecessors(); } // 判断 // 如果有线程在当前线程获取锁之前排队 也就是队列已经有元素了 但不是自己 返回true (有人排队) // 其他情况 返回false(没人排队) public final boolean hasQueuedPredecessors() { Node t = tail; // 队列尾指针 Node h = head;// 队列头指针 Node s;// 临时变量 return h != t && // 比较head tail 如果队列为空 h != t 为 false ((s = h.next) == null || // head的next为空 证明队列为空 s.thread != Thread.currentThread() // 如果head有next 也就是第一个等待线程 判断是不是自己 如果是自己 那嘿嘿 ); }
2. 获取锁后一系列的设置 都有什么可说的
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++; }
firstReader:记录着第一个获取读锁的线程
firstReaderHoldCount: 记录着第一个获取读锁的线程获取锁的次数
private transient Thread firstReader = null;// 线程 private transient int firstReaderHoldCount;// 数量
cachedHoldCounter:记录线程ID+次数
存储在ThreadLocal中 (面试弱引用 ThreadLocal如果能扯到这里 你就赢一半人了)
每个获取读锁的线程都存着
static final class HoldCounter { int count = 0; // Use id, not reference, to avoid garbage retention final long tid = getThreadId(Thread.currentThread()); }
readHolds:继承了ThreadLocal的类
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { // 这里定义了initialValue // ThreadLocal第一次初始化map会调用这个函数 public HoldCounter initialValue() { return new HoldCounter(); } }
总结: 就是把所有获取读锁的线程+次数 都存着
至于为什么这样存,我不明白作者的心思,或许跟当时HashMap为什么用头插法一样,这样记录在释放锁的时候更快? 不晓得了...
3. 看一下读锁的释放过程
ReadWriteLock rw = new ReentrantReadWriteLock(); rw.readLock().unlock();
ReentrantReadWriteLock#tryReleaseShared:
// AQS#releaseShared public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 如果返回true 也就是释放完锁后 当前线程的读锁已经没有了 // 没有的话 做一个doReleaseShared操作 可以简单理解为唤醒其他等待线程(队列里下一个等待的线程) doReleaseShared(); return true; } return false; } protected final boolean tryReleaseShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); // 查看firstReader是不是当前线程 firstReader在这里用到了 if (firstReader == current) { // assert firstReaderHoldCount > 0; // 判断firstReaderHoldCount如果等于1 把firstReader置空 // 也就是获取过一次锁 if (firstReaderHoldCount == 1) firstReader = null; else // 否则 就 减1 firstReaderHoldCount--; } else { // 如果不是首个获取读锁的线程 HoldCounter rh = cachedHoldCounter; // 判断cachedHoldCounter是不是当前线程的持有器 如果不是那就从readHolds(ThreadLocal)中获取 if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); // 判断持有锁次数 int count = rh.count; // <=1 移除ThreadLocal变量 记着 ThreadLocal最后一定要remove否则就是内存泄漏 if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } // 如果持有次数很多 那就减1 --rh.count; } // for (;;) { // state-SHARED_UNIT // SHARED_UNIT = (1 << SHARED_SHIFT) = 65536 int c = getState(); int nextc = c - SHARED_UNIT; // cas设置新值 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; } } // private void doReleaseShared() { // for (;;) { Node h = head; // h != null && h != tail 如果为true 表示队列中有等待线程 if (h != null && h != tail) { // 获取waitStatus waitStatus状态在之前文章中有提到过 是他后继线程给他的状态 int ws = h.waitStatus; // 如果状态是SIGNAL 需要唤醒 if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases // 唤醒 unparkSuccessor(h); } // 如果状态是0 就是不需要唤醒 设置为PROPAGATE状态 接着往后唤醒需要唤醒的人 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // 如果cas失败了 接着循环 loop on failed CAS } // 如果head变换了 需要接着循环 if (h == head) // loop if head changed break; } }
4. 注意
写这些知识为了抛砖,也不是很完善 , 其实可以自己写一个方法 跟着断点,进源码看看 那样更容易加深印象。
关于写锁的获取与释放 读者朋友自行研究吧 有问题可以给我留言 大家一起讨论哈
欢迎关注公众号: