AQS源码分析(JDK1.8)

1、简介

(1)AQS(AbstractQueuedSynchronizer)是实现各种锁的基础,因此必须要非常了解

(2)依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)

(3)AQS是Concurrent包的核心,lock就是在AQS的基础上实现的,阻塞队列,线程池,信号量等都离不开AQS的支持。

 

 

2、AQS框架设计

       它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:

  •  getState()
  • setState()
  • compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

 

3、自定义同步器需要自己实现的方法

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了自定义同步器实现时主要实现以下几种方法:

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

以上的获取和释放都是循环自旋CAS设置同步状态变量实现。独占模式下只需要实现tryAcquire(int)和tryRelease(int);而共享模式下只需要实现tryAcquireShare(int)和tryReleaseShare(int);特俗如读写锁,同时包含了独占模式和共享模式。

 

4、继承关系

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

 

5、源码详解(!!!)

AQS内部包含了2个内部类(Node和ConditionObject),及一系列方法,其中我们要重点与 获取/释放 同步状态相关的方法。

5.1、Node节点类

static final class Node {
        // 共享模式下等待的标记
        static final Node SHARED = new Node();
        // 独占模式下等待的标记
        static final Node EXCLUSIVE = null;
 
        // 线程的等待状态 表示线程已经被取消
        static final int CANCELLED =  1;
        // 线程的等待状态 表示后继线程需要被唤醒
        static final int SIGNAL    = -1;   //头节点一定是,但其他正常等待的节点也可能是
        // 线程的等待状态 表示线程在Condtion上
        static final int CONDITION = -2;
        // 表示下一个acquireShared需要无条件的传播
        static final int PROPAGATE = -3;
 
        /**
         *   SIGNAL:     当前节点的后继节点处于等待状态时,如果当前节点的同步状态被释放或者取消,
         *               必须唤起它的后继节点
         *         
         *   CANCELLED:  一个节点由于超时或者中断需要在CLH队列中取消等待状态,被取消的节点不会再次等待
         *               
         *   CONDITION:  当前节点在等待队列中,只有当节点的状态设为0的时候该节点才会被转移到同步队列
         *               
         *   PROPAGATE:  下一次的共享模式同步状态的获取将会无条件的传播
 
         * waitStatus的初始值时0,使用CAS来修改节点的状态
         */
        volatile int waitStatus;
 

        /**
         * 当前节点的前驱节点,当前线程依赖它来检查waitStatus,在入队的时候才被分配,
         * 并且只在出队的时候才被取消(为了GC),头节点永远不会被取消,一个节点成为头节点
         * 仅仅是成功获取到锁的结果,一个被取消的线程永远也不会获取到锁,线程只取消自身,
         * 而不涉及其他节点
         */
        volatile Node prev;
 
        /**
         * 当前节点的后继节点,当前线程释放的才被唤起,在入队时分配,在绕过被取消的前驱节点
         * 时调整,在出队列的时候取消(为了GC)
         * 如果一个节点的next为空,我们可以从尾部扫描它的prev,双重检查
         * 被取消节点的next设置为指向节点本身而不是null,为了isOnSyncQueue更容易操作
         */
        volatile Node next;
 

        /**
         * 当前节点的线程,初始化后使用,在使用后失效 
         */
        volatile Thread thread;
 
        /**
         * 链接到下一个节点的等待条件,或特殊的值SHARED,因为条件队列只有在独占模式时才能被访问,
         * 所以我们只需要一个简单的连接队列在等待的时候保存节点,然后把它们转移到队列中重新获取
         * 因为条件只能是独占性的,我们通过使用特殊的值来表示共享模式
         */
        Node nextWaiter;
 
        /**
         * 如果节点处于共享模式下等待直接返回true
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
 
        /**
         * 返回当前节点的前驱节点,如果为空,直接抛出空指针异常
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
 

        //下面是3个构造方法
        Node() {    // 用来建立初始化的head 或 SHARED的标记
        }
 
        Node(Thread thread, Node mode) {     // 指定线程和模式的构造方法
            this.nextWaiter = mode;
            this.thread = thread;
        }
 
        Node(Thread thread, int waitStatus) { // 指定线程和节点状态的构造方法
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

5.2、ConditionObject内部类

对于ConditionObject内部类,现在不做介绍。这个类将会在后面专门将Condition时做详细介绍。现在只需要知道它是使线程处于等待某个条件的作用即可(await()、signal()、signalAll() )

5.3、AQS的成员变量

//序列化标识(不需要背),只需要知道AQS实现了Serializable接口即可
private static final long serialVersionUID = 7373984972572414691L;

//注意:volatile(transient表示不会序列化)
private transient volatile Node head;  //同步队列头节点
private transient volatile Node tail;  //同步队列尾节点
private volatile int state;   //同步状态

//CAS自选下,时间很短时的时间。不是timeout
static final long spinForTimeoutThreshold = 1000L;

//下面的一些成员以后再补充,现在只需要了解(和JVM、反射有关)
private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

同步队列中首节点是获取到锁的节点,它在释放的时候会唤醒后继节点,后继节点获取到锁的时候,会把自己设为首节点。

注意,设置首节点不需要使用CAS,因为在并发环境中只有一个线程都获取到锁,只有获取到锁的线程才能设置首节点。

5.4、独占式

5.4.1、获取同步状态 acquire(int)

通过调用acquire的方法获取同步状态,该方法忽略中断,线程获取同步状态失败后,进入同步队列,在对其进行中断操作后,线程不会从同步队列移除(lock就是调用acqurire()方法实现的

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

/**
 * 当前线程的自我中断
 */
private static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

函数流程如下:

(1)tryAcquire(),获取同步状态,AQS并没有实现这个方法,具体的实现由它的继承类进行重写,比如ReentrantLock的Sync类。如果获取同步状态成功的直接返回true;如果获取同步状态失败的话,进行下面的调用。

(2)addWaiter(),将该线程封装成Node,加入等待队列的尾部,并标记为独占模式

(3)acquireQueued(),使节点自旋方式获取同步状态;获取成功则返回;获取失败,则挂起线程一直到获取到资源才返回。线程如果在获取同步状态中和同步队列中被中断过,要进行自我中断。在整个等待过程中被中断过,则返回true,否则返回false。

(4)selfInterrupt(),线程进行自我中断,前面的acquireQueued()被中断过返回true,就要进行自我中断

5.4.2、tryAcquire(int)

该方法需要自定义同步器自己来实现,AQS中没有实现。返回值为boolean类型。

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

AQS只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现吗?就是这里了!!!AQS这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了(通过state的get/set/CAS)!!!至于能不能重入,能不能加塞,那就看具体的自定义同步器怎么去设计了!!!当然,自定义同步器在进行资源访问时要考虑线程安全的影响。

5.4.3、addWaiter(Node mode)

将当前线程构造成节点,加入到同步队列尾部

/**
 * 把Node节点添加到同步队列的尾部
 */
private Node addWaiter(Node mode) {
    // 以独占模式把当前线程封装成一个Node节点
    Node node = new Node(Thread.currentThread(), mode);  
    // 尝试快速入队,如果失败,再调用enq(node)以死循环方式入队
    Node pred = tail;  // 当前队列的尾节点赋给pred
    if (pred != null) {  // 先觉条件 尾节点不为空
        node.prev = pred;  // 把pred作为node的前继节点
        if (compareAndSetTail(pred, node)) { //利用CAS把node作为尾节点
            pred.next = node;    // 把node作为pred的后继节点
            return node;       // 直接返回node
        }
    }
    enq(node);  // 尾节点为空或者利用CAS把node设为尾节点失败
    return node;
}
/**
 * 采用自旋的方式把node插入到队列中
 */
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 如果t为空,说明队列为空,必须初始化
            // 新建一个节点利用CAS设为头节点,就是这样的形式 head=tail=null
            if (compareAndSetHead(new Node())) 
                tail = head;
        } else {    // 尾节点不为空的情况
            node.prev = t;  // 把t设为node的前驱节点
            if (compareAndSetTail(t, node)) {  // 利用CAS把node节点设为尾节点
                t.next = node;   // 更改指针  把node作为t的后继节点
                return t;   // 直接返回t
            }
        }
    }
}

enq方法中采用了非常经典的自旋操作,只有通过CAS把node设为尾节点(volatile变量,写入后对其他线程立马可见)后,当前线程才能退出该方法,否则的话,当前线程不断的尝试,直到能把节点添加到队列中为止,这样就把并行添加变成了串行添加。

5.4.4、acquireQueued(Node, int)

上面一步是将当前线程构造成同步节点,并将节点以循环CAS方式添加到同步队列尾节点。

acquireQueued(Node,int)是是Node循环获取同步状态int,其实质上还是循环调用tryAcquire(int)。通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了。聪明的你立刻应该能想到该线程下一部该干什么了吧:进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己想干的事了。没错,就是这样!是不是跟医院排队拿号有点相似~~acquireQueued()就是干这件事:在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回

/* 
 * 此主要是通过自旋方式获取同步状态
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;   //标记是否成功拿去到资源
    try {
        boolean interrupted = false;  // 标记等待过程中是否被中断过
        //循环CAS获取资源
        for (;;) {
            final Node p = node.predecessor();  // 获取该节点的前驱节点p
           // 如果前驱节点是头节点并且能获取到同步状态(因为FIFO,只有头节点的后继节点才有资格获取)
            if (p == head && tryAcquire(arg)) {  
            //不用担心多线程发生竞争,因为同一时刻,获取到同步状态的线程只有一个,因此不用CAS
                setHead(node);                   // 把当前节点设为头节点
                p.next = null;                  // 把p的next设为null(相当于原来头节点出同步队列),便于GC
                failed = false;                 // 标志--表示成功获取同步状态,默认是true,表示失败
                return interrupted;             // (返回点)返回该线程在获取到同步状态的过程中有没有被中断过
            }
            if (shouldParkAfterFailedAcquire(p, node) &&   // 用于判断是否挂起当前线程
              parkAndCheckInterrupt())
                interrupted = true;  //如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true    
        }
    } finally {
        if (failed)    //如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
            cancelAcquire(node);
    }
}

整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安心的休息点,同时可以再尝试下看有没有机会轮到自己拿号。

下面看下shouldParkAfterFailedAcquire(p, node)方法

/**
 * 如果线程获取同步状态失败就要检查它的节点status,要保证prev = node.prev
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;  // 获取当前节点的前驱节点的waitStatus
    if (ws == Node.SIGNAL)     
        //如果前驱节点的ws = singal,表示前驱节点释放后会唤起当前线程,可以安全的挂起当前线程
        return true;   // 能够挂起当前线程直接返回true
    if (ws > 0) {   //就是Cancelled
         //前驱节点的ws > 0,说明ws = Cancelled,表示前驱线程被取消,从前驱节点继续往前遍历,直到找到第一个前驱节点的ws <= 0 为止
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {  //0 | PPROPAGATE
        //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

就是看前驱节点的状态:

  • signal,表示前驱节点释放同步状态后,会唤醒当前节点(因此当前线程可以安全挂起)
  • cancelled,表示前驱节点取消,应该继续向前找,知道第一个ws<=0(也就是不是取消状态的节点)
  • 初始状态(0)或者是propagate(传播状态),表示不能挂起当前线程,需要将前驱节点设置成signal状态才行

从这里可以看出signal并不一定是头节点的状态,其他正常等待的节点都可能是这个状态(当然头节点一定是signal)

下面看下parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);  //调用LockSupport工具挂起当前线程
    //Thread.interrupted()会清除当前线程的中断标记位
    return Thread.interrupted();  //返回当前线程是否被中断过
}

如果上一步的方法中前驱节点是signal,则表示可以让当前线程挂起,因此调用parkAndCheckInterrupt()方法。被唤醒的可能有2种:

  • 被unpark()
  • 被interrupt()

5.4.5、cancelAcquire(node)

获取同步状态失败(超时,或者中断),取消节点。

private void cancelAcquire(Node node) {
    // 当前节点不存在的话直接忽略 
    if (node == null)
        return;
 
    node.thread = null;  // 把当前节点的线程设为null
 
    // 获取当前节点的前驱pred
    Node pred = node.prev;
    while (pred.waitStatus > 0)   // 如果prde的ws > 0,直接跳过pred继续往前遍历,直到pred的ws <= 0
        node.prev = pred = pred.prev;  
 
    // 获取pred的后继predNext
    Node predNext = pred.next;
 
    // 把node节点的ws设为CANCELLED
    node.waitStatus = Node.CANCELLED;
 
    // 如果node是尾节点,利用CAS把pred设为尾节点,predNext为null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {   //node不是尾节点
        // pred不是头结点 && pred的线程不为空 && pred.ws = singal
        // 利用CAS把node的next设为pred的next节点
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
             pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);  //就是将中间的cancelled节点出队
        } else {   // node是头结点,唤起它的后继节点
            unparkSuccessor(node);
        }
 
        node.next = node; // node指向自己,便于GC
    }
}

主要做了6件事

  1. 找到node节点的第一个不是cancelled的前面的节点pre
  2. 设置node节点的状态为cancelled
  3. 如果node是尾节点,则将pre设置为尾节点,preNext=null
  4. 如果pre不是头节点,且pre为signal,则连接pre和node.next,就是将中间全部的cancelled节点出队
  5. 如果node是头节点,则唤醒node的后继节点
  6. node.next=node,方面node回收

下面介绍下unparkSuccessor(node)方法

/**
 * 如果node存在唤醒它的后继节点
 */
private void unparkSuccessor(Node node) {
    //获取node的ws,如果ws<0,使用CAS把node的ws设为0,表示释放同步状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
 
    /*
     * 获取node的后继节点s,根据条件s = null 或者 s.ws > 0,从同步队列的尾部开始遍历,
     * 直到找到距node最近的满足ws <= 0的节点t,把t赋给s,唤醒s节点的线程
     * 如果s不为null && s的ws <= 0,直接唤醒s的线程
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);   //唤醒节点s对应的线程
}

就是先把node的状态CAS成0,释放同步状态

然后取得node的后继节点next。如果node=null或者node.waitStatus=cancelled,则找到距离node最近的节点s(从tail往前找),满足s.waitStatus<=0,则将s赋值给next。降下来唤醒next节点对应的线程即可。

5.4.6、获取同步状态总结(独占式)

 

5.4.7、释放同步状态(独占式)release(int)

unlock()即使调用这个。先释放同步状态,再唤醒后继节点(包含原来头节点出队列)。

/**
 * 以独占模式释放同步状态,当前线程释放同步状态的时候,会唤醒同步队列上的后继节点
 * 释放成功后之后直接返回true
 */
public final boolean release(int arg) {
    if (tryRelease(arg)) {  //其实现一般为循环CAS实现,很大可能是返回true
        Node h = head;  //获取头节点
        if (h != null && h.waitStatus != 0)  //头节点不为空,且状态不为0(没有释放)
            unparkSuccessor(h);  //唤醒头节点的next节点(其中就包含了将头节点状态设置为0,以及对next节点为空|cancelled等情况的处理)
        return true;
    }
    return false;
}

它是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自定义同步器在设计tryRelease()的时候要明确这一点!!

5.4.8、tryRelease(int)

具体自己实现

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

 

5.4.9、响应式中断获取同步状态  acquireInterruptibly(int arg)

/**
 * 当前线程被中断后,直接抛出异常,否则的话,再次调用tryAcquire方法获取同步状态
 */
public final void acquireInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();   //如果线程被中断了,抛出异常
    if (!tryAcquire(arg))  //获取同步状态失败
        doAcquireInterruptibly(arg);   //中断式获取同步状态
}

5.4.10、doAcquireInterruptibly(int arg)

这个方法和acquire()方法很相似,只不过线程在被中断后直接抛出异常,综合了acquire() + addWaiter() + acquireQueued()

/**
 *  以独占模式获取同步状态,线程被中断直接抛出异常
 */
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    //也是在尝试tryAcquire()失败之后,将当前线程创建节点,加入到同步队列的尾节点
    final Node node = addWaiter(Node.EXCLUSIVE);  
    boolean failed = true;
    try {
        for (;;) {  //死循环来获取同步状态(对中断敏感)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();   //响应中断,退出
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

 

5.4.11、指定时间内获取同步状态  tryAcquireNanos(int arg, long nanosTimeout)

响应中断,且有时间限制

/**
 * 以独占模式获取同步状态,线程被中断,直接抛出异常,如果在指定时间内没有获取到同步状态,
 * 直接返回false,表现获取同步状态失败.
 */
public final boolean tryAcquireNanos(int arg, long nanosTimeout)                             
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //如果获取状态成功,则不会执行do....;如果获取状态失败,则执行do...
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

5.4.12、doAcquireNanos(int arg, long nanosTimeout)

private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    if (nanosTimeout <= 0L)  //时间参数异常
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);  //将当前线程装成Node,添加到同步队列的尾节点
    boolean failed = true;
    try {
//就是如果前驱是头节点,则尝试获取;如果获取失败,或者前驱不是头节点,则进入带时间线程unpark()
        for (;;) {  //死循环获取同步状态
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                etHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();  //计算剩余时间
            if (nanosTimeout <= 0L)
                return false;  //超时
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)  //剩余时间大于CAS自旋界限时间,如果剩余时间小于...,则自旋CAS
                LockSupport.parkNanos(this, nanosTimeout);  //超时悬挂线程
            if (Thread.interrupted())  //影响中断
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

在第一次尝试获取同步状态失败之后,进入这个方法。先将当前线程装成节点,加入同步队列尾部。然后进入死循环。如果前驱是头节点,则尝试获取;如果获取失败,或者前驱不是头节点,则进入带时间线程unpark()。过程中带时间限制,响应中断。

 

5.5、共享式

最大的特点就是:同一时刻允许多个线程获取到同步状态

5.5.1、获取同步状态  acquireShared(int arg)

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)  //尝试获取同步状态
        doAcquireShared(arg);  //进入等待队列,直到获取资源
}

acquireShared方法就是先调用tryAcquireShare来获取同步状态,如果获取失败,则进入等待队列,循环获取。整个过程忽略中断。与独占式的区别是,独占式的返回值boolean,而共享式是int,可能取值如下:

  • 0:获取成功,没有剩余资源
  • n:获取成功,还有剩余资源n供其他线程获取
  • <0:获取失败

5.5.2、doAcquireShared(int)

将当前线程加入等待队列尾部休息,直到其他线程释放资源唤醒自己,自己成功拿到相应量的资源后才返回

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) {//如果到head的下一个,因为head是拿到资源的线程,此时node被唤醒,很可能是head用完资源来唤醒自己的
                int r = tryAcquireShared(arg);//尝试获取资源
                if (r >= 0) {//成功
                    setHeadAndPropagate(node, r);//将head指向自己,还有剩余资源可以再唤醒之后的线程
                    p.next = null; // help GC
                    if (interrupted)//如果等待过程中被打断过,此时将中断补上。
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            
            //判断状态,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

和独占式的acquireQueued基本一样,区别在于:

  • ryAcquireShare和tryAcquire的返回值类型不一样
  • setHead(node)和 setHeadAndPropagate(node, r);//将head指向自己,还有剩余资源可以再唤醒之后的线程。因为独占式一个获取同步状态之后,其他线程不能获取,因为唤醒后继节点在头节点释放同步的时候。而共享式可以多个线程同时获取同步状态,因此,一个线程获取之后,还要循环其他线程来获取同步状态。
  • selfInterrupt()放到doAcquireShared()里了,而独占模式是放到acquireQueued()之外

5.5.3、setHeadAndPropagate(Node, int)

设置node为头节点,同时唤醒后继节点(因为同一时刻可以有多个线程获取同步状态)

private void setHeadAndPropagate(Node node, int propagate) {  //propagate是tryAcquireShare的返回值
    Node h = head; // 保存当前的头节点
    setHead(node); // 把当前节点设为头节点
    /*
     * 这里有三种情况执行唤醒操作:1.propagate > 0(还有剩余资源),代表后继节点需要被唤醒
     *                          2. h节点的ws < 0或者 h=null
     *                          3. 新的头结点为空 或者 新的头结点的ws < 0
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;    // 找到当前节点的后继节点s
        if (s == null || s.isShared())   // s=null 或者 s是共享模式,调用doReleaseShared方法唤醒后继线程
            doReleaseShared();
    }
}

在设置头节点成功之后,由于是共享模式,如果剩余资源数>0 | 头节点=null或者状态为cancelled,则应该唤醒后继节点

5.5.4、doReleaseShared()

唤醒后继节点

private void doReleaseShared() {
    /*
     * 注意,这里的头结点已经是上面新设定的头结点了,从这里可以看出,如果propagate=0,
     * 不会进入doReleaseShared方法里面,那就有共享式变成了独占式.
     */
    for (;;) {  // 这里一个死循环直到满足条件h=head才能跳出
        Node h = head;
        if (h != null && h != tail) {  // 前提条件-当前的头结点不为null && h不是尾节点
            int ws = h.waitStatus;   
            if (ws == Node.SIGNAL) {  // 如果当前头结点的ws=signal,利用CAS把h的ws设为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);  // 唤醒头结点的后继节点
            }   // 如果h的ws=0,就把h的ws设为PROPAGATE,表示可以向后传播唤醒
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                
        }
        if (h == head)  // 如果头结点没有发生改变,表示设置完成,可以退出循环
            break;      // 如果头结点发生了变化,可能被唤醒的其他节点重新设置了头结点
    }                   // 这样头结点发生了改变,要进行重试,保证可以传播唤醒信号
}

5.5.5、releaseShared(int arg)

释放资源,唤醒后继节点

//释放同步状态
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {  // 尝试释放资源
        doReleaseShared();  //释放资源成功,则唤醒后继节点
        return true;
    }
    return false;
}

5.5.6、tryReleaseShared(int arg)

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

AQS中没有具体实现这个方法,在自定义同步器中去实现。

 

5.5.7、共享式中断式获取  acquireSharedInterruptibly(int arg)

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();  //响应中断
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);  //中断式获取
}

5.5.8、doAcquireSharedInterruptibly(arg)

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                 throw new InterruptedException();  //响应中断
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

和doAcquireShared(int)基本类似,只是添加了响应中断的代码

 

6、总结

独占式

 

共享式

之后说下关于函数的开头:

  1. try开头的AQS没有具体实现,除了 tryAcquireNanos(int arg, long nanosTimeout) 和  tryAcquireSharedNanos(int arg, long nanosTimeout)
  2. 获取同步状态变量那步一定是 tryAcquire 和 tryAcquireShare
  3. 响应中断的是  acquireSharedInterruptibly 和  acquireInterruptibly,他们分别调用 doAcquireSharedInterruptibly 和 doAcquireInterruptibly
  4. 超时获取的是 tryAcquireNanos 和 tryAcquireSharedNanos,他们分别调用  doAcquireNanos 和 doAcquireSharedNanos
  5. 所以以do开头的都是响应中断|超时获取里面调用的
  6. 在顶层调用的是 acquire、acquireShare、响应中断、超时获取 那几个
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值