并发编程理论 - AQS源码分析

目录

AQS核心公用方法

enq【将节点加入队列(尾部)】

addWaiter【为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部】

acquireQueued【以独占并且非线程中断模式获取已经存在于队列中的线程】

unparkSuccessor【唤醒CLH中的下一个节点】

doReleaseShared【共享模式唤醒CLH中的一个节点或设置传播(唤醒多个)】

独占模式

    acquire【获取独占锁】

    release【释放独占锁,唤醒管程的“下一个”节点线程】

共享模式

    acquireShared【获取共享锁,阻塞剩余资源数的线程】

    releaseShared【释放共享锁,唤醒管程的可用节点线程】


    AQS源码大致分为独占(acquire【获取独占】、release【释放独占】)和共享(acquireShared【获取共享】、releaseShared【释放共享】)两种模式,而其AQS模板方法会调用tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively的模板方法,并且都会抛出UnsupportedOperationException(不支持的操作异常)那么就说明不能直接使用AQS来处理业务,而是需要在子类中实现该5个方法才能使用。而Node子类中的方法都比较简单就不做分析了,但是ConditionObject中的等待(await*)和唤醒(signal*、signalAll)相关的方法,也是核心需要专门进行分析,AQS的虚拟双向队列与多个ConditionObject条件队列的关系。

AQS核心公用方法

enq【将节点加入队列(尾部)】

/**
 * 将一个节点插入CLH双向队列,必要时初始化
 * node.prev = t; 加上 t.next = node; 那么CLH增加了当前节点,并且又成环了
 * @param node the node to insert
 * @return 返回节点的上一个节点
 */
private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
    for (;;) { // 自旋
        AbstractQueuedSynchronizer.Node t = tail;
        if (t == null) { // 尾部为null,说明还没有初始化过,则CAS初始化,只有一个节点head = tail
            if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
                tail = head;
        } else {
            // t是tail,当前节点的前缀链接指向CLH的尾节点
            node.prev = t;
            // CAS设置到尾部,并将当前节点添加到尾部
            if (compareAndSetTail(t, node)) {
                t.next = node; // 将原来的CLH的最后一个节点的tail执向当前节点
                return t;
            }
        }
    }
}

addWaiter【为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部】

/**
 * 为当前线程和参数传入的模式 创建节点并排队排队插入CLH尾部
 * 整个方法与enq差不多, 只是添加的节点设置了 nextWaiter属性
 * 
 * @param mode Node.EXCLUSIVE 独占模式, Node.SHARED 共享模式
 * @return 返回一个新的节点
 */
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
    // 为当前线程和参数传入的模式 创建节点
    AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), mode);
    // 先加入到尾部,但是可能没有初始化(则 tail为null)
    AbstractQueuedSynchronizer.Node pred = tail;
    if (pred != null) {
        // 将当前节点的前置链接设置为原来的尾部
        node.prev = pred;
        // CAS将尾部由原来替换为现在节点
        if (compareAndSetTail(pred, node)) {
            // 把原来的尾部节点的 next链接指向当前节点
            pred.next = node;
            return node;
        }
    }
    // 说明AQS队列可能还没初始化
    enq(node);
    return node;
}

acquireQueued【以独占并且非线程中断模式获取已经存在于队列中的线程】

/**
 * 以独占并且非线程中断模式获取已经存在于队列中的线程
 * 该方法用于Condition的等待方法获取锁
 *
 * @param node 独占节点
 * @param arg the acquire argument 子系统自己定义的state值
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
    boolean failed = true; // 获取默认失败
    try {
        boolean interrupted = false;
        for (;;) {
            // 当前节点的前置节点设置为临时变量 p
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            // 如果前一个节点就是CLH的第一个节点了,则意味着该节点可以进行尝试抢占资源
            if (p == head && tryAcquire(arg)) {
                // 将自己设置为CLH第一个节点,则其他属性置位空。由于这里是独占模式,那么只有该节点是唯一持有资源state的线程
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 到这里说明还有多个节点,那么就要将上一个节点状态设置为 signal(下一个节点挂起,如果到该节点则需要唤醒下一个节点),
            if (shouldParkAfterFailedAcquire(p, node) &&
                    // 在把下一个节点的线程挂起
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//这个方法就体现了并发编程中挂起线程的范式思想。具体请参照上文。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        //如果前置节点的状态已经是signal,意味着已经经历过二次尝试,可以返回true,让线程进入等待状态。
        return true;
    if (ws > 0) {
        //如果前置节点处于取消状态,则不断的向前回溯,直到找到一个不是取消状态的节点。无论如何,至少head节点不会是取消状态,所以最终一定可以找到一个不是取消状态的前置节点。然后将该node的pre指针指向该非取消状态节点。在这个循环中就将AQS中的内部队列的长度缩短了。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //将前置节点的状态变更为signal。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //返回false,提示调用者线程仍然需要继续尝试,不可以进入休眠状态
    return false;
}

//让线程进入等待状态,并且返回线程的中断信息
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

 

unparkSuccessor【唤醒CLH中的下一个节点】

    管程模型中当上一个节点执行完成后肯定要唤醒下一个等待的节点,这里就相当于 synchronized时,monitorexit时需要唤醒下一个等待的线程,执行任务。

/**
 * 唤醒CLH队列中的下一个节点(如果存在,自己是最后一个那就没有下一个了),
 */
private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
    // 获取当前节点的状态
    int ws = node.waitStatus;
    // 如果不是取消和无状态
    if (ws < 0) // 将当前节点设置为无状态 0
        compareAndSetWaitStatus(node, ws, 0);

    // 获取当前节点的后一个节点
    AbstractQueuedSynchronizer.Node s = node.next;
    // 如果下一个节点为null, 或者下一个节点的被取消了(waitStatus > 0只能是被取消了CANCELLED=1)
    if (s == null || s.waitStatus > 0) {
        s = null; // 将节点置位空
        // 从CLH的尾部进行遍历,因为从前找的链路已经断了
        for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) // 非CANCELLED的节点
                s = t; // 就唤醒该节点
    }
    // 再判断,如果后一个节点不为null,则直接唤醒下一个节点的线程 LockSupport.unpark => UNSAFE.unpark
    if (s != null)
        LockSupport.unpark(s.thread);
}

doReleaseShared【共享模式唤醒CLH中的一个节点或设置传播(唤醒多个)】

/**
 * 共享模式的释放锁操作
 */
private void doReleaseShared() {
    // 自旋操作
    for (;;) {
        // 获取CLH第一个节点
        AbstractQueuedSynchronizer.Node h = head;
        // 第一个节点不是空,并且不是最后一个节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 获取并判断第一个节点的状态,如果是SIGNAL状态,则要去唤醒
            if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
                // CAS将当前节点的SIGNAL(要去唤醒别人)修改为无状态
                if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
                    continue;            // loop to recheck cases, 失败了再自旋
                unparkSuccessor(h); // 上面分析过了unparkSuccessor,唤醒头节点的下一个线程
            }
            // 将下一个设置为传播行为
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 判断一个第一个节点有没有被改变过,改变了就跳出自旋
        if (h == head)                   // loop if head changed
            break;
    }
}

 

 

 

独占模式

    acquire【获取独占锁】

  获取独占锁:先调用一次子类实现。再将CLH队列中加入一个独占模式的节点,再调用acquireQueued将线程挂起,把上一个节点的状态设置为SIGNAL,则只有上一个节点能唤醒该节点线程。

/**
 * 该方法是排他模式, 忽略中断异常. 
 * 1、至少会回调一次(因为里面自旋还可能调用)子类实现的tryAcquire方法,返回成功
 * 2、接着就会将当前线程排队直到成功
 * 否则:不满足1、2 则调用selfInterrupt, Thread.currentThread().interrupt(); 以抛出中断异常
 * 
 * 这个方法可以用于实现 {@link Lock#lock}功能
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
        selfInterrupt();
}

  

    release【释放独占锁,唤醒管程的“下一个”节点线程】

/**
 * 这个方法用于使用 {@link Lock#unlock}. 即独占模式的释放锁资源,并且唤醒等待锁的下一个线程
 * @return 返回的结果就是子类自己实现的 {@link #tryRelease}的结果
 */
public final boolean release(int arg) {
    // 调用子类的实现,返回true再执行(即现回调一次子类)
    if (tryRelease(arg)) {
        // 获取CLH的头节点, 因为当前执行的不一定就是自己
        AbstractQueuedSynchronizer.Node h = head;
        // 如果头节点部位空,并且状态不是无状态
        if (h != null && h.waitStatus != 0)
            //上面分析过unparkSuccessor方法,会唤醒head节点的下一个节点或者从后往前找的第一个有效节点 的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

共享模式

    acquireShared【获取共享锁,阻塞剩余资源数的线程】

/**
 * 获取共享模式的锁,忽略中断。
 * 至少回调一次子类的 {@link #tryAcquireShared} 因为自旋中还会调用可能多次
 */
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
/**
 * 共享模式获取锁
 * @param arg the acquire argument
 */
private void doAcquireShared(int arg) {
    // 将共享模式节点和当前线程,添加到CLH的队尾
    final AbstractQueuedSynchronizer.Node node = addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋
        for (;;) { // 获取当前节点的上一个节点
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            // 如果上一个节点是队头
            if (p == head) {
                // 获取子类实现,因为只有子类知道还能共享的个数(比如限流器设置允许同时5个任务)
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 与独享模式一样,  到这里说明还有多个节点,那么就要将上一个节点状态设置为 signal(下一个节点挂起,如果到该节点则需要唤醒下一个节点),在把下一个节点的线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

/**
 * Sets head of queue, and checks if successor may be waiting
 * in shared mode, if so propagating if either propagate > 0 or
 * PROPAGATE status was set.
 *
 * @param node the node
 * @param propagate the return value from a tryAcquireShared
 */
private void setHeadAndPropagate(AbstractQueuedSynchronizer.Node node, int propagate) {
    AbstractQueuedSynchronizer.Node h = head; // 记录原来的head节点,下面使用
    setHead(node); // 将当前节点设置为头节点
    // 唤醒后继结点
    //1.共享资源剩余数大于0,有剩余资源肯定是要唤醒后继结点的
    //2.头结点不存在。可以看到这个条件会与后文的head!=null相冲突。而且实际上在这个方法执行的时候,head节点是必然存在的,不可能为null。留待下篇文章再做解读。
    //3.头结点状态小于0.这里只有两种可能,一种是SIGNAL(-1),一种PROPAGATE(-3)。这两种数值的出现都意味着后继节点要求node(也就是当前head)唤醒后继结点
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
        AbstractQueuedSynchronizer.Node s = node.next;
        // 在AQS的实现类中,存在着所谓读写锁。也就是说在AQS的内部队列,共享节点和独占节点是都存在的。所以共享唤醒传播到独占节点就要停止
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

 

    releaseShared【释放共享锁,唤醒管程的可用节点线程】

    共享模式那么可以运行多个线程执行,比如信号量(Semaphore、CountDownLatch)。比较形象的就是限流器(信号量实现的限流器:后续添加链接),只能同时允许5个线程执行,那么当前线程执行完成,则可以允许一个或者多个(有可能当前执行的远小于5个),那么需要唤醒一个或多个线程。

/**
 * 释放共享模式的锁,唤醒一个或多个等待该锁的线程
 * @return 返回值就是子类自己实现的 {@link #tryReleaseShared}的返回值
 */
public final boolean releaseShared(int arg) {
    // 还是回调一次子类自己的实现
    if (tryReleaseShared(arg)) {
        // 调用公用的释放共享(多个)线程
        doReleaseShared();
        return true;
    }
    return false;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值