AQS共享锁源码分析

本文详细解析了AQS(AbstractQueuedSynchronizer)中的共享锁实现,主要涉及Semaphore和CountDownLatch两个类。通过源码分析,阐述了获取锁、释放锁的过程,特别是`doAcquireShared`和`setHeadAndPropagate`方法的逻辑,指出共享锁允许多个线程同时持有并尝试唤醒后续等待线程的特点。
摘要由CSDN通过智能技术生成

上一篇博客介绍了AQS中的排他锁,现在说一下AQS中的共享锁。

共享锁的主要实现就是semaphore,countDownLatch。这两个类在应用中还是比较多的,尤其是countDownLatch,在控制主线程和子线程同步的时候,经常会看见。

源码解析

获取锁

    public final void acquireShared(int arg) {
        // 剩余锁的资源是否大于0
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
private void doAcquireShared(int arg) {
        // 设置锁的模式为共享锁,接入到队尾,并且设置成tail
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 前一个node
                final Node p = node.predecessor();
                if (p == head) {
                    // 尝试获取锁
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 设置head并且传播
                        // 这边和排他锁有点不一样,排他锁设置成head就可以了,这边还要再传播
                        // 具体逻辑下面再分析
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        // 判断中间是不是有打断过
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 和排他锁一致,把前面的节点设置成signal
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) // park唤醒时候判断是被unpark唤醒还是interrupt
                    interrupted = true;
            }
        } finally {
            if (failed)
                // 如果异常,取消获取锁,锁的状态改成CANCELLED
                cancelAcquire(node);
        }
    }

其实这边的逻辑只有setHeadAndPropagate不一致,其他都是一致的,这边主要来看下这个方法

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // 既然当前线程拿到了锁,就把当前node设置为head
        setHead(node);
        // propagate是还剩余的资源
        // h == null ,可能是其他线程在释放的时候,前一步p.next=null后垃圾回收了
        // h.waitStatus<0,需要向后唤醒
        // (h =head)==null, h指向了新创建的head,同样可能被释放后垃圾回收 
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 向后节点是共享节点
            // s == null, 我觉得还是可以像之前那样理解,node被指向了head,
            // 然后被其他线程释放了p.next=null,所以next==null
            if (s == null || s.isShared())
                // 释放锁,并且向后唤醒
                // 在release时候也会调用此方法
                doReleaseShared();
        }
    }

 

因为共享锁可以多个线程持有锁,所以在获得锁的时候,也会尝试唤醒后面的共享锁的线程,源码中也说了,可能会存在无效唤醒。但是也没有什么问题,拿不到也没关系,就会再次阻塞,和排他锁的流程也是一致的。再来看下doReleaseShared方法。

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            // 如果head != tail
            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
            }
            // ③
            // head是否改变
            if (h == head)                   // loop if head changed
                break;
        }
    }

其实这边代码有一些点:

①:如果在原来有后继节点的时候,因为共享锁可能有多个,这段代码可能也会多个执行,保证只有一个在运行。

②:ws=0那说明这个节点是尾部节点,那既然在①的时候head节点不是SIGNAL了。说明在①的时候,这个尾部节点变成了head节点,此时ws=0,但是后面的cas却失败了,那就可能是再有一个线程加入了进来,在执行到后面compareAndSetWaitStatus时候有个新的node加进来把waitStatus设置成了SIGNAL,这时继续走获取head唤醒流程。

③:如果head没有变化,说明没有人更改head,整个流程执行结束。如果head变化,重新循环,唤醒后面的节点。

 

至此AQS共享锁也分析结束了。和排他锁主要区别是排他锁只有一个线程能拿到锁,共享锁可以多个线程拿到锁,并且在拿到锁的同时,也会尝试去唤醒后面的线程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值