02_ReentrantReadWriteLock源码分析

类结构

简介

跟ReentrantLock原理一样,都是可以重入,公平/非公平的互斥锁。内部虽然有一个读锁和写锁,但是用的都是同一个Sync。
在原理上跟ReentantLock不同的是,当所有线程去获取读锁的时候,是不会阻塞的。只有当线程持有写锁/读锁,另一个线程再去获取读锁/写锁的时候,才会阻塞。总之,读读不会阻塞,其余都会阻塞。(不包括两次获取锁都是同一线程的情况->锁升级)

锁升级是指当前线程持有读锁的时候,再去获取写锁,反之也是升级。但是需要注意,==只有持有写锁,再去获取读锁的时候,才能升级成功。== 持有读锁,再去获取写锁,必须要先释放读锁,才能去获取写锁。

还有跟ReentantLock不同的是,state不在是0可以获取锁,1获取不到锁。而是用int 的state位标识当前锁的状态。state的高16位代表读锁的状态,0代表可以获取读锁,1代表不能获取读锁。同理低16位没有1就是没有人获取写锁。



        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

加锁流程

场景一:多线程获取读锁

代码

        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() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
            ....
                return 1;
            }
            return fullTryAcquireShared(current);
        }

如果没有人获取写锁,这时候获取读锁非常的简单,就是state的高位+1。

场景一:多线程获取读锁写锁

加锁

  • 一开始t1去获取锁,因为没有竞争,获取锁成功,setOwner为自己,state的低八位加一。
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                ...
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
  • 然后t2又来获取读锁。
    1. 先是获取锁,发现写锁已经被占了,且不是当前线程,获取锁失败了。
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    1. 获取锁失败后,去调用doAcquireShared(1)方法。这个跟之前ReenrantLock的阻塞然后排队方法一样,都是创建一个空的Node作为head,然后把当前阻塞的节点跟在head后面。且设置head的waitStatus为-1.然后调用LockSuport.park停住线程。唯一不同的是,t2获取的是读锁,他的节点类型是Node.SHARED
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 同理t3,t4都以此挂在了head后面,根据获取锁的不同分为Node.SHARED和Node.EXCLUSIVE。到此加锁的流程结束了。

解锁

  • 第一步是t1先释放锁,把state的低八位减一。这时候如果state的低八位为0代表没重入,释放成功,setOwner为null。
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


    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;
        }
  • 第二步,检查head是否不为空,且head的waitstatus是否不等于零,如果是就去unpark其他排队的线程。

唤醒其他线程之前,我们先看下其他获取读锁的线程是在哪儿被park的

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//① 获取前驱节点
                if (p == head) {
                    int r = tryAcquireShared(arg); //② 再次获取锁,state 的高八位+1
                    if (r >= 0) {
                        setHeadAndPropagate(node, r); ③ 设置自己节点为头节点,然后唤醒后继节点如果是shared的,结束。
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) //注意注意: t2,t3都是在这里被park的,unpark后,从这里唤醒,回到上面第一步。
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

以下是如何唤醒其他线程

    1. t2是在doAcquireShared(1)复活,这个时候又一次循环后,进入tryAcquireShared(1)方法获取锁,获取锁后,state的高八位+1。
    1. t2获取锁成功后,第一件事就是setHeadAndPropagate把自己设置成head,然后唤醒下一个如果是shared的节点。同理下个节点唤醒后第一件事就是把自己设置成head,然后唤醒下一个,直到下一个的下一个不是shared或者已经是tail了为止,程序中止。
      ==注意因为被唤醒的是读锁线程,那他是有责任唤醒他后面连续排队的获取读锁线程,即读锁的传播==
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);

        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
    
    private void doReleaseShared() {

        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容