ReentrantReadWriteLock源码分析

概述

ReentrantReadWriteLock是一个可重入的读写锁,写锁独占,读锁共享,支持公平和非公平两种方式加锁,源码可以看到,内部的Sync类重写了AQS中的tryAcquire,tryAcquireShared方法已经对应的释放方法。
独占式锁和共享式锁都已经分析很多了,今天我们简单看下ReentrantReadWriteLock的源码。测试例子依然可以在github中看到

ReentrantReadWriteLock 类结构

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    //读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    //AQS的子类
    final Sync sync;
    
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    ...
}

我们知道锁的实现都依赖于Sync这个AQS的子类,一起看看Sync的类结构吧

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;

    //state的值 高16位表示读锁(或者共享锁)的次数, 低16位表示写锁(独占锁)的次数(注意重入也会累加)
    static final int SHARED_SHIFT   = 16;
    //所以读锁加锁成功一次会加一个SHARED_UNIT
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    //最大加锁量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    //写锁计算的因子,高16位全是0,低16位全是1,state值相与就是写锁量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    //读锁量或者叫共享量  因为是高16位表示,计算具体值是左移16位
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //计算写锁量 将state和EXCLUSIVE_MASK相与计算写锁量
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    //线程占用的读锁量(重入性)
    static final class HoldCounter {
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread());
    }

    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    
    private transient ThreadLocalHoldCounter readHolds;

    //记录最新获取读锁线程的缓存计数
    private transient HoldCounter cachedHoldCounter;

    //第一个获取读锁的线程
    private transient Thread firstReader = null;
    //第一个获取读锁线程的计数
    private transient int firstReaderHoldCount;

    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }
    。。。
}

我们以非公平的读锁加锁过程举例:\

public void lock() {
    sync.acquireShared(1);
}
protected final int tryAcquireShared(int unused) {
    /*
     * 1.如果被其他线程加了写锁,直接返回失败
     * 2. 否则线程不应该阻塞,没达到最大值,进行CAS增加State数量
     *      成功则加锁成功,如果是第一线程加锁赋值firstReader ,
     *      如果不是第一个线程,判断最新的读锁线程是不是自己,不是则将自己赋给最新线程,
     *      增加HoldCounter计数(本线程的读锁量)
     * 3. 如果第二步失败,走完整的CAS替换流程
     */
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        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 != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

readerShouldBlock是一个抽象方法,公平子类和非公平子类复写该方法,我们看下非公平子类的readerShouldBlock方法逻辑

final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    //如果队列中第一个是写等待,则返回true
    //防止写等待一直被阻塞着,毕竟读是共享的,人多抢的多
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

上面我们看到如果第二步没有抢到读锁则进入充分请求锁阶段fullTryAcquireShared

final int fullTryAcquireShared(Thread current) {
    
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        //如果其他线程占了写锁,返回加锁失败
        if ((c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        } else if (readerShouldBlock()) {
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                //不是重入的,则返回-1
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        //到了这里就是应该加读锁的
        if (sharedCount(c) == MAX_COUNT) //读锁量最大后抛出异常
            throw new Error("Maximum lock count exceeded");
        //进行CAS替换,替换成功则加锁成功,进行必要的属性赋值就好了(和上面的快速加锁逻辑一致)
        //替换失败,其他线程读锁加成功了,for循环再来一次
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

上面就是加非公平读锁的逻辑了,要先看AQS源码篇哦。
公平读锁的逻辑只有readerShouldBlock方法不一样,公平读锁的readerShouldBlock方法,只要该线程不是队列的第二节点,就返回true,这里就不详细看了。
读锁的释放

public void unlock() {
    sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //进行本线程的读锁量操作,如果是firstReader操作firstReader,否则得到HoldCounter操作
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    //CAS操作state, state减为0,读锁完全释放,唤起后继节点
    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;
    }
}

下面我们看看写锁的加锁过程

public void lock() {
    sync.acquire(1);
}
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. state不为0,说明被加了读锁或写锁,判断写锁为0或者写锁不是自己,则返回失败.
     * 2. 如果写锁满了返回失败,如果是自己重入的写锁,直接修改state的值,返回加锁成功
     * 3. 如果写锁应该阻塞或者CAS设置state失败(说明加锁失败),返回false.
     *    CAS 设置成功,设置独占线程为自己,返回加锁成功
     */
    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;
}

非公平加写锁,writerShouldBlock总是返回false

final boolean writerShouldBlock() {
    return false; // writers can always barge
}

公平加写锁也是只有writerShouldBlock方法不同,公平加写锁writerShouldBlock方法需要判断是否是第二节点。这里就不详细介绍了。
写锁的释放

public void unlock() {
    sync.release(1);
}
protected final boolean tryRelease(int releases) {
    //判断是否是独占线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //state减去释放量,如果减为0,说明全部写锁释放,返回true,进行后继节点的唤醒。
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342