AQS——同步队列共享模式

共享模式下的acquireShared()和releaseShared()

首先来看看acquireShared()代码,

public final void acquireShared(int arg) {
        // 尝试获取资源如果成功直接返回
        if (tryAcquireShared(arg) < 0)
            // 当前线程调用doAcquireShared()加入同步队列
            doAcquireShared(arg);
    }

tryAcquireShared()方法同样由自定义同步器实现,用来给state原子的加一些操作。如果tryAcquireShared()<0说明当前线程尝试获取资源失败需要进入同步队列,来看看doAcquireShared()代码,

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) {
                    // 当前节点尝试获取资源,如果r>=0
                    // 说明有资源可以获取
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 与独享模式不一样的是,当前节点获取到资源后
                        // 会unpark()当前节点的下一个节点进行tryAcquireShared()
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        // 如果在等待过程中,外界给与中断干扰
                        // 则获取到资源后会响应中断(有可能是取消使用资源)
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 与独享模式一样,前一个节点是SIGNAL时
                // 进行park挂起当前线程等待唤醒
                // 如果前一个节点不是SIGNAL或者前一个节点被取消
                // 需要从尾部找到前一个非null节点
                // 最后将前一个节点置为SIGNAL状态并继续执行for循环
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

共享模式较之独享模式的setHeadAndPropagate(),

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

与独享模式不一样的是,当前节点获取到资源后还会唤醒下一个节点进行资源的获取。

再来看看releaseShared()方法,

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

首先尝试释放资源方法,如果前一个或几个线程已经释放了共享资源(与独享模式不一样的是,独享模式保证当state最初设定的值时才会唤醒下一个节点),那么tryReleaseShared()方法会返回true,然后执行doReleaseShared()方法唤醒下一个节点,被唤醒的节点会尝试tryAcquireShared()方法。

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 头节点的状态为SIGNAL唤醒后继节点
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;               
            }
            // 如果唤醒的后继节点获取到资源将头节点改变
            // 退出循环
            if (h == head)                   
                break;
        }
    }

共享模式流程

  1. 假定我们有10个资源,A线程正在占用7个,B线程正在占用2个,剩余1个资源
  2. 这时C线程需要获取资源,但是需要4个资源才能继续执行,所以C线程到来时初始化同步队列并将C线程加入到同步队列
  3. 此时A线程释放掉一个资源,唤醒头节点的下一个节点尝试资源的获取,此时共有2个资源可以获取,所以C获取资源失败
  4. 同时又来了一个线程D尝试获取资源,D需要3个资源才能继续执行,D加入同步队列进行挂起等待资源获取
  5. 此时A再次释放了一个资源,唤醒头节点下一个节点,失败(注意!!!这里体现共享锁的公平性与非公平性,A再次释放资源后空闲3个资源,而D正好需要3个资源就可以继续执行,如果是公平的共享锁则只会按照队列顺序进行排队获取资源;如果是非共享锁,则D此时可以获取资源进行下一步操作)
  6. A再次释放2个资源,B释放2个资源,尝试唤醒C,成功,并且C成为头节点后再次唤醒D也成功(体现共享的一步),头节点执行的节点是获取到资源的节点
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值