JUC源码分析7-locks-AQS-共享模式

AQS中一定要记住2点:

1.处理流程:

if(!请求成功)

加入队列

2.请求是对state的判断,AQS不关心你state表示什么,你可以表示状态也可以表示数量,由子类实现对请求的判断。将规则的判断和规则的处理分离,有点像模板模式。

先想想什么是独占什么是共享,举个栗子:独占就像大家拿号去排队体检,你拿号了发现前面还有n个人,没办法,等吧,然后你前面的人体检完了,医生就说,你通知下一位吧,ok,你出来通知排你后面的人,这个人有可能是跟占座位似得就放在纸在哪,所以你跳过他,再通知后面真正有人的进去。而共享则不同了,这个号可能不止属于你一个人,可能属于你公司所有体检的人,所以拿号排队轮到你的时候,你就需要通知排队的所有同事,大家一起体检去啊(这个共享的不太恰当,应该当成condition例子来说才好)。感觉独占和共享的大概意思就是这样。

还是看下AQS的共享模式

public final void acquireShared(int arg) {
//tryAcquireShared请求判断又是由子类实现判断
    if (tryAcquireShared(arg) < 0)
    //失败后加入队列
        doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//节点状态是共享,之前的独占模式是EXCLUSIVE
//跟独占一样加入队列
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        //还是判断pre是不是头结点,是就再次请求
            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);
    }
}

发现共享整个挂起的doAcquireShared方法跟独占模式的acquireQueued处理差不多,唯一有区别的似乎就是如果pre节点是头结点,当前节点请求成功,独占模式只是将节点设置为head然后return,这里除了sethead还将唤醒队列的中其他节点,其他方法不管,继续看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) {
        Node s = node.next;
        //当前node请求成功后判断next节点是共享节点就继续release
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
private void doReleaseShared() {
    /*
			使用for保证队列中节点一定会被传递,即使有其他acquire或release在进行
     */
    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;            // 失败了就loop
                unparkSuccessor(h);
            }
            //为0就设置成PROPAGETE表示需要传播
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

这里最重要的是理解doReleaseShared,一个进程doReleaseShared,然后unparkSuccessor,这时候被唤醒的其他线程可能线程切换运行,重新请求修改head节点,没机会的话,这里检查head节点没有变化就继续for,一直等到被唤醒的线程时间片切换到,然后再将继续修改下一个节点,到最后队列中的所有节点都被唤醒。

这里一定要想着线程切换多看看几遍,我当时就郁闷了半天这个疑问。

对应的acquireSharedInterruptibly响应中断和tryAcquireSharedNanos响应中断超时,跟独占的都差不多。

共享模式release,这个没什么好说的,释放判断成功就doReleaseShared,把队列中所有节点release

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

共享跟独占的流程图差不多,不想画了,改天学习下AQS的condition。

参考:

http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer

时间: 2024-08-02 11:15:28

JUC源码分析7-locks-AQS-共享模式的相关文章

JUC源码分析-集合篇(三)ConcurrentLinkedQueue

JUC源码分析-集合篇(三)ConcurrentLinkedQueue 在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法.使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现.非阻塞的实现方 式则可以使用循环 CAS 的方式来实现.本节让我们一起来研究一下 Doug Lea 是如何使用非阻塞的方式来实现线程安全队列 ConcurrentLinkedQueue 的,相信从大师

JUC源码分析-集合篇(五)BlockingQueue 阻塞式队列实现原理

JUC源码分析-集合篇(五)BlockingQueue 阻塞式队列实现原理 以 LinkedBlockingQueue 分析 BlockingQueue 阻塞式队列的实现原理. 1. 数据结构 LinkedBlockingQueue 和 ConcurrentLinkedQueue 一样都是由 head 节点和 last 节点组成,每个节点(Node)由节点元素(item)和指向下一个节点(next)的引用组成,节点与节点之间就是通过这个 next 关联起来,从而组成一张链表结构的队列.默认情况下

JUC源码分析-集合篇(七)PriorityBlockingQueue

JUC源码分析-集合篇(七)PriorityBlockingQueue PriorityBlockingQueue 是带优先级的无界阻塞队列,每次出队都返回优先级最高的元素,是二叉树最小堆的实现. PriorityBlockingQueue 数据结构和 PriorityQueue 一致,而线程安全性使用的是 ReentrantLock. 1. 基本属性 // 最大可分配队列容量 Integer.MAX_VALUE - 8,减 8 是因为有的 VM 实现在数组头有些内容 private stati

JUC源码分析16-集合-ConcurrentSkipListMap、ConcurrentSkipListSet

NBA这赛季结束,勇士可惜啊,谁能想到没拿到冠军,库昊也没成为真正的老大,lbl一战封神,所有口水留言都变成羡慕嫉妒恨,哎,我库啊,还是还是看书吧. ConcurrentSkipListMap说实话,之前还真没注意过,还是看JUC才看到,利用skiplist跳表结构来实现一种有序的map,之前看到的map都是无序.在学习前还是要好好了解下什么是skiplist跳表,的确很不错,利用空间换时间,复杂度为logN,跳表的原理参考http://kenby.iteye.com/blog/1187303,

JUC源码分析10-locks-CountDownLatch

上一次学习了ReetrantLock,是对AQS独占模式的,这次学习CountDownLatch,是共享模式api的实现.人生不死,学无止境.先看个demo吧: import java.util.concurrent.CountDownLatch; public class CountDownLatchTest { private static CountDownLatch count1 = new CountDownLatch(1); private static CountDownLatch

JUC源码分析6-locks-AQS-独占模式

AbstractQueuedSynchronizer(下面简称AQS),javadoc说明: Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on  first-in-first-out (FIFO) wait queues. 1.提供一个FIFO等待队列,使用方法伪代码表示就是: Acquire: if(!获取到锁

JUC源码分析13-locks-ReentrantReadWriteLock

ReentrantReadWriteLock基于AQS实现读写锁的同步: 1.利用共享模式实现读锁,独占模式实现写锁: 2.支持公平和非公平,非公平的情况下可能会出现读锁阻塞写锁的场景: 3.写锁阻塞写锁和读锁,读锁阻塞写锁: 4.写锁可以降级为读锁,读锁不能升级为写锁,只能先release再lock: 5.写锁支持condition条件: 6.读写锁都支持超时/中断lock: 7.适合读多写少的场景. 实现ReadWriteLock接口,用于返回读/写锁: <span style="fo

Java源码分析:深入探讨Iterator模式

作者:兄弟连 java.util包中包含了一系列重要的集合类.本文将从分析源码入手,深入研究一个集合类的内部结构,以及遍历集合的迭代模式的源码实现内幕. 下面我们先简单讨论一个根接口Collection,然后分析一个抽象类AbstractList和它的对应Iterator接口,并仔细研究迭代子模式的实现原理. 本文讨论的源代码版本是JDK 1.4.2,因为JDK 1.5在java.util中使用了很多泛型代码,为了简化问题,所以我们还是讨论1.4版本的代码. 集合类的根接口Collection

JUC源码分析8-locks-AQS-condition

AQS的conditionObject实现类似object的wait/notify/notify的功能,功能大概是: 1.object维护一个监视器和一个等待队列,condition对于一个lock可以有多个condition,对于每个condition维护一个条件队列: 2.提供wait/signal/signalall功能. 来个入门demo: public class ConditionTest { private static ReentrantLock lock = new Reent