读写锁_ReentrantReadWriteLock原理

读写锁_ReentrantReadWriteLock原理

读写锁用的是同一个Sycn同步器,因此等待队列,state等也是同一个

场景:t1 线程 writeLock().lock() t2线程readLock().lock(), t3线程readLock().lock() t4 线程 writeLock().lock()

1.t1成功上锁

流程与ReentrantLock加锁相比没有什么特殊之处,不同是写锁占了state的低16位,读锁使用的是state的高16位。

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire:c!=0表示可能加了读锁或者写锁,w==0:w是写锁,等于0表示加的是读锁,和t1互斥,直接false,反之执行 || 后面 current != getExclusiveOwnerThread()检查是否是自己重入,不是别人的写锁也是互斥,就false,w + exclusiveCount(acquires) > MAX_COUNT表示重入超过16位最大数值(65535)则抛异常(几乎不会发生),最后 setState(c + acquires);写锁计数加1。

如果c等于0就是没有开始加锁,writerShouldBlock()看是否是公平锁或者非公平锁,非公平锁永远返回false,公平锁会检查队列,compareAndSetState尝试将写锁改为1(CAS操作),成功就返回false,最后setExclusiveOwnerThread,设置当前线程为owner线程

protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    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;
}

image-20220821010250806

2.t2尝试加锁

t1线程写锁加入成功, t2 线程执行readLock().lock()读锁,进入sync.acquireShared(1)流程,首先进入tryAcquireShared(1)流程。如果有写锁占着,返回-1表示失败

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

tryAcquireShared返回值:

  • -1:表示失败
  • 0:表示成功,但是后续节点不会去唤醒
  • 正数表示成功,而且数值大小表示还有几个节点需要唤醒,读写锁返回1

tryAcquireShared流程:拿到当前线程和state状态,先判断有没有加写锁,t1是加过写锁的所以继续执行,getExclusiveOwnerThread() != current判断加写锁的不是自己,不是自己条件成立就返回-1,是自己返回1。因为可能一个线程先加写锁后加读锁可以锁降级。因为t1加的是写锁,t2明显不是自己,就返回-1

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();//拿到当前线程
    int c = getState();//获得state状态
    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);
}

image-20220821135157570

acquireShared:在上一步acquireShared流程中tryAcquireShared(arg) < 0成立,进入doAcquireShared`

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

doReleaseShared:首先将t2加入到节点中,此时还是活跃状态,还没有park,然后进入死循环,寻找当前线程节点的上一个节点,如果上一个节点是head,表示当前线程具有争抢的资格,tryAcquireShared尝试争抢,失败就会进入 if (shouldParkAfterFailedAcquire(p, node)流程,判断前驱是否是-1并将前驱节点设置-1,让前面的唤醒他,第一次返回false重新执行上面的方法,这样会继续去判断资格,尝试争抢,再次失败回到 shouldParkAfterFailedAcquire中,这次成功就会 parkAndCheckInterrupt()现在将线程parkz

private void doReleaseShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//加入共享类型的节点	
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {//死循环
            final Node p = node.predecessor();//寻找t2的前驱节点
            if (p == head) {//如果前驱节点是head,表示t2是老二,有资格争抢锁
                int r = tryAcquireShared(arg);//所以尝试争抢锁,-1失败,1成功
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&//判断前驱是否是-1并将前驱节点设置-1,让前面的唤醒他,第一次返回false重新执行上面的方法
                parkAndCheckInterrupt())//现在将线程park
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220821140109813

同理这样t3,t4进入node,不同的是因为t2,t3是读锁,所以是Shared共享状态,t4写锁是Ex独占状态

image-20220821142010272

3.t1 unlock

unlock

public void unlock() {
    sync.release(1);
}

**releasetryRelease**成功会将OwnerThread设为null,判断头节点是否是空,和是否不是0(判断有无后继节点),如果成立就会唤醒后继节点

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease:state-1,查看exclusiveCount是否是0,如果不是0,表示这时一次锁的重入。如果减成0了,将OwnerThread设为null,返回true

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;
}

image-20220821185002981

doAcquireShared:**release流程中unparkSuccessor(h);会将后继节点唤醒,唤醒的地方在加锁时park住的地方:doAcquireShared流程中的parkAndCheckInterrupt处,唤醒后,进入死循环,判断是否是第二位,再进入tryAcquireShared去竞争锁,加入锁成功,r = 1,setHeadAndPropagate**将节点重新替换

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);
    }
}

tryAcquireShared:判断exclusiveCount个数是不为0,跳过,r是高16位读锁,compareAndSetState(c, c + SHARED_UNIT)是让高16位加1,读锁加成功

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    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)) {//让高16位加1
        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);
}

image-20220821190012597

setHeadAndPropagate:setHead设置之前节点为头节点,node.next拿到当前节点下一个节点 t3,如果t3 isShared是否是共享节点。条件满足,进入doReleaseShared

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

doReleaseSharedcompareAndSetWaitStatus(h, Node.SIGNAL, 0)把头节点状态由 -1 改成 0,成功后将头节点后继节点唤醒,也就是把t3唤醒,t3 也是和 t2一样阻塞在同一个地方,最后也到这一步,不满足isShared。这样实现了读读并发

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    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;
    }
}

image-20220821190900122

4.t2 unlock,t3 unlock

public void unlock() {
    sync.releaseShared(1);
}

releaseShared t2先执行,tryReleaseShared将读锁计数-1,如果减为0才返回true,由于之前计数是二,releaseShared流程不会执行下面,t3再次执行,就会将state置为0,且tryReleaseShared流程是true,进入doReleaseShared

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared 从下面死循环看,拿到state,减去高位的1,compareAndSetState成功了,看state是否为0,是0才返回true

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    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;
    }
    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;
    }
}

doReleaseSharedws == Node.SIGNAL:头节点是否是-1,是的话compareAndSetWaitStatus将头节点从-1改成0,成功后unparkSuccessor唤醒头节点的后继节点t4,t4是在 acquireQueued中阻塞

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    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;
    }
}

acquireQueued:看次节点是否是老二,tryAcquire尝试获取写锁,获取成功

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//暂停处
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220821191340393

image-20220821191809974

image-20220821192911747

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值