深入jdk源码系列---关于AQS独占式获取同步状态相关源码解析

AQS:全程AbstractQueuedSynchronizer,是Java用来构建锁或者其他同步组件的基础框架,它使用一个int类型的成员变量state来表示同步状态,通过一个内置的FIFO(先进先出)队列来完成资源获取线程的派对工作。AQS借鉴了CLH队列锁的思想。
CLH队列锁是基于链表的可扩展,高性能,公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱节点的主题,假设前驱节点释放了锁,则该节点结束自选。
大致流程图:
在这里插入图片描述
AQS的主要使用了模板设计模式,子类通过集成并涉嫌它的抽象方法来管理同步状态state。抽象方法来实现过程中需要对同步状态进行控制,所以AQS提供了三个方法进行操作(getState()、setState(int new State)和CompareAndSetState(int expect,int update)),这三个方法都是并发安全的。注意:这个三个都是final,不允许重写的。
我们的重点是关注AQS给我们提供的模板方法,通过对模板方法的实现,从而达到我们想要的想法。
protected boolean tryAcquire(int arg)
独占式获取同步状态,实现该方法需要查询当前状态并判断当前状态是否符合预期,然后再进行CAS设置同步状态。
protected boolean tryRelease(int arg)
独占释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected int tryAcquireShared(int arg)
共享式获取同步状态,返回大于等于0 的值,表示获取成功,否则获取失败。
protected boolean tryReleaseShared(int arg)
共享式释放同步状态
protected boolean isHeldExclusively()
当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程锁独占。
AQS提供我们控制状态state标志的三个方法

获取状态标志位

protected final int getState()

设置当前状态标志位

protected final void setState(int newState)

使用CAS设置当前状态

protected final boolean compareAndSetState(int expect, int update)

AQS的数据结构:AQS是对于CLH队列锁的变相实现,它本质还是队列锁,所以他的数据结构是队列。通过队列中的节点来报存一下重要信息,主要报存一下信息:

volatile Thread thread;用于存处当前节点所有线程
volatile int waitStatus;用于存处当前节点的状态
volatile Node prev:用于存处该节点的前置节点
volatile Node next;用于存处当前节点的后置节点
Node nextWaiter;用于存处condition条件下的Node节点

waitStatus它是用于保存当前节点的状态的,而Node中已经规定好了线程的状态:分别为:
CANCELLED:值为1,表示线程的获锁请求已经“取消”
SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足
PROPAGATE:值为-3,当线程处在“SHARED”模式时,该字段才会被使用上

由于AQS分为两种模式,分别是共享模式和独占模式,所以节点也需要区别这两种模式,所以采用了两种节点模式来表示,

static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

SHARED:表示线程以共享的模式等待锁(如ReadLock)
EXCLUSIVE:表示线程以互斥的模式等待锁(如ReetrantLock),互斥就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁。
当前线程获取同步状态失败时候,同步器会将当前线程以及等待状态信息构建成一个节点并将其加入同步对象,同时会阻塞当前线程,当同步状态释放时,会把收节点唤醒,使其再次获取同步状态。同步队列中的节点(Node)用来存同步状态失败的线程引用、等待状态以及前驱节点和后继节点。
head和tail
AQS还拥有首节点(head)和尾节点(tail)两个引用,一个指向队列头节点,而另一个指向队列尾节点。注意:head不保存线程信息。
重点源码解析了:
独占式通过调用同步器的acquire(int arg)方法可以获取同步状态。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //响应中断请求
            selfInterrupt();
    }

首先会调用到你重写的tryAcquire的方法,判断是否能获取同步状态,
如果获取成功,则发生短路现象,后面代码,不继续执行,如果获取失败,则继续往下执行。
接着就会调用到addWaiter(Node.Exclusive)方法。
这个方法主要是将线程构造成节点,并采用尾插法将节点插入到链表中来。

private Node addWaiter(Node mode) {
    //将线程构造成节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //尾插法
    Node pred = tail;
    //如果尾节点不为空,则将新节点插入到链表尾部
    if (pred != null) {
        node.prev = pred;
        //将尾节点变成当前新构建的节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //能走到这,证明链表是空的,需要初始化链表
    enq(node);
    return node;
}

Enq(node)可以理解为初始化同步链表,

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        //如果尾节点是空的,证明链表是空,初始化链表
        if (t == null) { // Must initialize
            //创建一个空节点,并把这个空节点设置为头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //将当前当前线程构建的节点进行尾插法插入。
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

,该方法构造同步节点(独占式,Node.EXCUSIVE,同一时刻只能有一个线程成功获取同步状态),然后加入到同步队列尾部。最后会调用到acquireQueued(Node node,int arg)方法。
我们目光主要看acquireQueued方法。
该方法的作用是:将节点以自旋+阻塞的方式进行获取同步状态,直到线程获取成功,或者出现异常,才能退出。

/**
 * 主要是将自身节点进行自旋+阻塞的方式进行获取同步状态
 * @param node
 * @param arg
 * @return
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        //这里用于中断这个代码的执行
        // if (!tryAcquire(arg) &&
        //        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //        selfInterrupt();

        boolean interrupted = false;
        for (;;) {
            //获取当前节点的前置节点
            final Node p = node.predecessor();
            //判断端节点的前置节点是否为头节点,如果是则尝试获取同步状态
            if (p == head && tryAcquire(arg)) {
                //如果该节点的前置节点的为头节点,且获取同步状态成功,则将自己设置为头节点
                //头节点的特点:前置节点为空,且节点中的线程为空
                setHead(node);
                //释放原先的头节点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            /**
             * shouldParkAfterFailedAcquire
             * 该方法用于判断前置节点的状态,
             * 如果前置节点状态为就绪状态即:waitStatus=-1,则直接返回true
             * 如果前置节点取消锁请求,则将前置节点移除队列,知道当前节点的前置节点为非取消太,即小于0
             * 如果前置节点状态小于-1则将前置节点状态通过CAS变成-1
             * 最后返回false
             * 所以只有当前置节点为就绪态,才会执行parkAndCheckInterrupt()方法
             * parkAndCheckInterrupt该方法就是将当前线程阻塞
             */
            //shouldParkAfterFailedAcquire(p, node)
            // 如果p节点为就绪状态则需要将当前线程阻塞
            //如果p节点为非就绪状态时
            // 由于需要对链表进行调整,是一个很耗时的操作,完成好了,很可能链表中的很多节点都移除链表,所以没进行阻塞,从而提高效率
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
               //当线程唤醒,且中断标志值被设置为true才会走到这里
                interrupted = true;
        }
    } finally {
        //出现异常或者,原节点变为头节点才会走到这一步,判断节点是否成功获取锁,如果成功则返回false
        //不走判断,若果失败,情况为出现异常此时需要将节点退出请求队列。
        if (failed)
            //出现异常会走到这里
        /**
         * 将本身节点移除队列
         */
            cancelAcquire(node);
    }
}

其中方法内调用的尤为重要的方法为shouldParkAfterFailedAcquire(),这个方法方法主要的作用是将节点设置为到,前置节点为就绪态的节点后面。这里有个性能优化的地方,如果节点的原前置节点就是就绪态,代码执行很快,所以需要将线程阻塞,此时返回true,如果当前节点的前置节点非就绪态,此时代码执行会比较慢,可能执行完后很多节点以及退出了队列,所以此时就不让他阻塞,直接进行下一次获取同步状态的过程中来。

/**
 * 该方法的作用是将节点的插入到链表中,切保证节点的前置节点为就绪态
 * @param pred
 * @param node
 * @return
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前置节点的状态
    int ws = pred.waitStatus;
    //判断前置节点处于什么状态
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        //如果原前置节点为,准备就绪状态.返回true
        return true;
    //判断前置节点是否取消了申请锁
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
        //将当前节点的前置节点变成前置节点的前置节点,知道前置节点的状态<=0结束
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        //将最终的前置节点的后继节点变成当前节点
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        //使用CAS将前置节点的标志位修改为-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //当节点的原前置节点的状态为非就绪状态时返回false
    return false;
}

还有一个比较重要的方法就是退出队列,cancelAcquire()这个方法进行退出操作,主要作用是将这个节点移除同步队列

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    //判断节点是否为空,如果为空直接返回
    if (node == null)
        return;
    //将节点内的线程置空
    node.thread = null;

    // Skip cancelled predecessors
    //获取节点的前置节点
    Node pred = node.prev;
    //将节点的前置节点置为取消态,一致寻找到前置节点为非取消态
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    //获取最新的前置节点的后继节点
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    //将当前节点的状态改为取消态
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    //判断当前节点是否为尾节点,如果是,通过CAS操作将节点的最新前置节点变成尾节点
    if (node == tail && compareAndSetTail(node, pred)) {
        //通过CAS操作将最新尾接节点的后继节点置为空
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        //判断最新前置节点是否为头节点
        //如果是判断前置节点的状态是否为就绪态或者(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);
        } else {
            //唤醒当前节点
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值