前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >话说 ReadWriteLock 第二篇

话说 ReadWriteLock 第二篇

原创
作者头像
木子的昼夜
修改2021-04-06 11:08:00
3450
修改2021-04-06 11:08:00
举报

ReadWriteLock 第二篇

提示:看了 ReadWriteLock 第一篇 才能看这一篇 ,关于ReadWriteLock 知识点明白上一篇讲的内容应付一般面试没什么问题了。

1. hasQueuedPredecessors

上一篇在获取读共享锁流程中有一个判断 ,

代码语言:txt
复制
 if (!readerShouldBlock() && 

如果readerShouldBlock返回false 那就正常获取锁,如果返回true那么就结束获取锁

这里说一下公平锁这个方法的内容:

代码语言:txt
复制
// 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. 获取锁后一系列的设置 都有什么可说的
代码语言:txt
复制
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: 记录着第一个获取读锁的线程获取锁的次数

代码语言:txt
复制
private transient Thread firstReader = null;// 线程
private transient int firstReaderHoldCount;// 数量

cachedHoldCounter:记录线程ID+次数

存储在ThreadLocal中 (面试弱引用 ThreadLocal如果能扯到这里 你就赢一半人了)

每个获取读锁的线程都存着

代码语言:txt
复制
static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = getThreadId(Thread.currentThread());
}

readHolds:继承了ThreadLocal的类

代码语言:txt
复制
static final class ThreadLocalHoldCounte
    extends ThreadLocal<HoldCounter> {
    // 这里定义了initialValue 
    // ThreadLocal第一次初始化map会调用这个函数
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

总结: 就是把所有获取读锁的线程+次数 都存着

至于为什么这样存,我不明白作者的心思,或许跟当时HashMap为什么用头插法一样,这样记录在释放锁的时候更快? 不晓得了...

3. 看一下读锁的释放过程
代码语言:txt
复制
ReadWriteLock rw = new ReentrantReadWriteLock();
rw.readLock().unlock();
01.png
01.png

ReentrantReadWriteLock#tryReleaseShared:

代码语言:txt
复制
// 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. 注意

写这些知识为了抛砖,也不是很完善 , 其实可以自己写一个方法 跟着断点,进源码看看 那样更容易加深印象。

关于写锁的获取与释放 读者朋友自行研究吧 有问题可以给我留言 大家一起讨论哈

欢迎关注公众号:

公众号二维码.jpg
公众号二维码.jpg

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ReadWriteLock 第二篇
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档