ReentrantLock共享锁源码分析

原文链接:https://blog.csdn.net/anlian523/article/details/106598739

1.共享锁Semaphore

  • 在Semaphore这里可以看到同样实现了公平和非公平锁,但是和独享锁的区别就是修改状态的方式,使用的是CAS+while循环来进行加锁或者说是解锁。因为可能会同时好几个线程进入来获取锁,是否能获取锁依靠的是permits也就是一开始的锁到底有多少。
  • 而且tryAcquireShared返回的是三个状态
  1. <0就是获取失败
  2. 0成功但是下次会失败
  3. 大于0就是获取成功
  abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        Sync(int permits) {
    
    
            //初始化给了多少把锁
            setState(permits);
        }

        final int nonfairTryAcquireShared(int acquires) {
    
    
            //CAS+循环获取锁
            for (;;) {
    
    
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
	}

//非公平锁
    static final class NonfairSync extends Sync {
    
    
        protected int tryAcquireShared(int acquires) {
    
    
            return nonfairTryAcquireShared(acquires);
        }
    }
//公平锁
    static final class FairSync extends Sync {
    
    
        protected int tryAcquireShared(int acquires) {
    
    
            for (;;) {
    
    
                //判断队列是不是有节点
                if (hasQueuedPredecessors())
                    return -1;
                //抢锁
                int available = getState();
                int remaining = available - acquires;//剩下多少锁
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

2.共享锁的获取

acquireShared

  • 对应独享锁的acquire,不同地方就是acquire的tryAcquire只有两种状态要么行要么不行,但是共享锁有三种,而且看上去没有了addWaiter,其实是放到了doAcquireShared
  • 而且doAcquireShared没有返回值,原因是它把selfInterrupt放到了doAcquireShared里面
 public final void acquireShared(int arg) {
    
    
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared

  • 首先是设置的waiter不同,这里设置的是shared。共享,但是独享锁是设置EXCLUSIVE。addWaiter被放到了这里
  • selfInterrupt也被放到这里了
  • 还有不同的地方就是善后工作使用的是setHeadAndPropagate,能够执行doReleaseShared继续唤醒下面的线程(类似于unparkSuccessor在独享锁里面的过程)。
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);
        }
    }

setHeadAndPropagate

  • 前面的shouldParkAfterFailedAcquire通知前面的节点唤醒自己。
  • 中间一段判断很难理解
  • if (propagate > 0 || h == null || h.waitStatus < 0 ||
    (h = head) == null || h.waitStatus < 0) 这段的意思其实就是,propagate其实就是tryAcquireShared传过来的r(获取共享锁之后的返回值)肯定是>=0的,那么propagate代表的就是还剩多少共享锁,如果propagate>0那么就可以继续释放线程获取锁。因为doReleaseShared就是调用了unparkSuccessor并且增加了一些CAS处理。h==null和(h=head)==null就是检查头结点是不是个空节点(不会发生的)

那么最后的h.waitSatus<0到底是什么意思呢?propagate的作用?

  1. propagate是多个线程竞争的情况,线程A唤醒旧head的后继节点,把旧head的状态改成0,线程B也要进入去唤醒head的后继节点,发现head的状态是0正在唤醒,那么就再把head改成Propagate。等到被唤醒的线程C获取锁失败执行shouldParkAfterFailedAcquire的时候就会尝试去修改head为signal。
  2. 总结来说就是为了避免重复唤醒,线程A来唤醒C,线程B其实也是唤醒C所以只是修改了状态为propagate。而C会获取锁失败的原因就可能是被插队了。
  3. 还有一种解释是,h.waitStatus<0的原因只有可能是propagate,因为在线程A唤醒当前线程C的时候head的waitSatus设置为0,被唤醒的c正在处理setHeadAndPropagate这样的操作(获取锁的情况),而这个时候线程B也是得到了旧head,想要唤醒线程C,为了避免重新唤醒所以是把head状态设置为propagate,而且C已经获取了锁,并且要执行doReleaseShared,而刚进来setHeadAndPropagate(Node node, int propagate)的时候propagate(也就是doAcquireShared)可能刚好没有共享锁,但是后面线程B给释放出来了,所以在这里PROPAGATE也可以说是提醒线程C已经有共享锁了。

总结:避免重复唤醒和提醒被唤醒线程已经有共享锁了

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) {
    
    //如果多个线程唤醒其它线程,可能会导致竞争线程无法唤醒,因为head要等唤醒处理完才能够继续唤醒(但是如果head没有处理完切换到其它竞争线程的情况),那么就改head的状态为PROPAGATE来通知被唤醒的线程还有共享锁被释放了
            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;
        }
    }

3.共享锁的释放

releaseShared

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

doReleaseShared

  • 这个函数的作用其实就是唤醒下一个节点,他和独享锁不同的地方就是doReleaseShared是可以被多个线程调用的。
  • 这个时候调用线程可能是CAS改成0失败的,也可能是队尾元素。
  • 如果head是null或者是队尾元素那么就可以结束循环
  • 如果不是空的,而且不是队尾元素说明队列至少有两个节点
  • 然后尝试CAS修改signal为0换成中间状态,让其他线程得知已经有人释放head的后继节点,如果多个线程同时唤醒head的后继节点就会出现前一个唤醒之后,被唤醒线程在setHeadAndPropagate修改了head节点导致后继节点不同。

head是0的时候状态

  • 队列只有一个节点的时候
  • 新加入节点,但是还没有执行shouldParkAterFailedAcquire
  • 最后一种就是唤醒线程之后,被唤醒线程还没有更改头结点为自己的原本的节点的时候的中间状态。

那么什么时候会是PROPAGATE?

参考上面

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 &&//head的后继节点正在被修改,那么就需要在head做一个提示,说明还有共享锁被释放了。用于在被唤醒节点以为没有共享锁的时候没有再去唤醒下一个节点
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

源码思维导图(重点是doReleaseShared和setHeadAndPropagate)

猜你喜欢

转载自blog.csdn.net/m0_46388866/article/details/120897610