AQS的常用方法

AQS的常用方法----本次是非公平锁为例(ReentrantLock中的NonfairSync)

Lock方法

当我们使用ReentrantLock的lock方法进行加锁时,实际上是使用了其内部类(Sync)的 sync.lock()方法进行加锁操作。可以追溯到一个抽象的lock方法,本次以非公平锁为例。
在这里插入图片描述
JDK8的源码

final void lock() {
            //初始化state的默认值为0
            //通过CAS比较替换的方式将state的值设置为1       
            if (compareAndSetState(0, 1))
            //获取当前的线程,将独占模式同步的当前所有者(可以理解为工作区)设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //通过CAS比较替换的方式不能成功修改state的值时进入
                acquire(1);
        }

接下来我们通过对应的方法带入一个例子。前提是了解过主要相关的参数。

  • 第一次进入lock方法,此时对应的线程为A通过CAS将同步状态state修改为1。并将自己放入了工作区中,方法结束。看看流程图
    在这里插入图片描述

acquire方法

acquire方法的传参默认为1,实际上调用的是其父类的(AbstractQueuedSynchronizer)中的acquire方法。其中的核心方法有3个组成
tryAcquire(arg)
addWaiter(Node.EXCLUSIVE), arg)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

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

tryAcquire方法

tryAcquire方法最终调用了nonfairTryAcquire方法,传参默认为1。

先分析方法

final boolean nonfairTryAcquire(int acquires) {
            //获取到当前线程
            final Thread current = Thread.currentThread();
            //获取到同步状态
            int c = getState();
            if (c == 0) {  //表示当前的工作区可用即无任何线程
                //通过CAS将同步状态state修改为1
                if (compareAndSetState(0, acquires)) {
                    //将当前线程放入工作区中
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //获取到工作区中的线程和当前线程进行比较
            //可以看出ReentrantLock是可重入锁通过修改state表示同步次数
            else if (current == getExclusiveOwnerThread()) {
                //将state的同步表示加上传参的值
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //设置新的同步标识值    
                setState(nextc);
                return true;
            }
            return false;
        }
  • 此时线程B进入了Lock方法,不会进入if块中,因为同步状态的标识被线程A改为了1,工作区的线程是A不是线程B。最终会调用到我们刚刚分析的这个方法。看以看出条件都不会满足,直接返回false。接下来看看addWaiter(Node.EXCLUSIVE), arg)做了什么??此时的流程图还是没有什么太大的变化。

addWaiter方法

该方法有两个参数分别是Node.EXCLUSIVE类和arg,分别是排他模式的一个Node类和参数1。分析源码。

 private Node addWaiter(Node mode) {
        //获取线程和对应的标记的模式封装成一个Node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //将Node的尾部赋值给了一个变量
        Node pred = tail;
        //判断是否不为null
        if (pred != null) {
            //将pred赋值给了node节点的prev上一个引用
            node.prev = pred;
            //通过CAS比较Node的Tail(尾部)
            if (compareAndSetTail(pred, node)) {
                //把当前node赋值给了pred的下一个引用
                pred.next = node;
                return node;
            }
        }
        //分析enq方法
        enq(node);
        return node;
    }
private Node enq(final Node node) {
        //死循环,即满足条件的自旋
        for (;;) {
            //将尾部节点赋值给了变量t
            Node t = tail;
            //非空判断
            if (t == null) { // Must initialize
                //通过CAS初始化Head节点
                if (compareAndSetHead(new Node()))
                    //将初始化的头部节点赋值给了尾部节点
                    tail = head;
            } else {
                //将变量t赋值给了node节点的前一个引用
                node.prev = t;
                //通过CAS将尾部节点修改为node节点
                if (compareAndSetTail(t, node)) {
                    //把t的下一个引用指向了node
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • 此时我们知道nonfairTryAcquire()方法返回的是一个false,经过!tryAcquire(arg)运算为true,此时进入了addWaiter(Node.EXCLUSIVE), arg)这个方法。开始分析。
  • 将线程B封装成了一个Node节点,将尾部节点赋值给了变量pred,pred此时是为null的,经过判断我们得知进入了enq(final Node node)这个方法。因为AQS队列是没有数据的。接下来分析enq(final Node node)这个方法。
  • 可以看到里面是一个死循环(满足特定条件的自旋)。将尾部节点赋值给了变量t,判断成立进入compareAndSetHead(new Node())方法,直接在队列head创建了一个空的Node节点(虚节点),并将头部的节点赋值给了尾部(tail = head)。看看此时的流程图发生了什么变化。
    在这里插入图片描述
  • 创建了一个空的Node节点数据,作为队列的第一个。循环继续,条件并不满足。把尾部的节点赋值给了变量t,此时变量t是不为null的,进入了else块中。把t赋值给了Node节点的前一个引用,通过CAS将尾部节点修改成了Node节点,最后将Node节点赋值给了t的下一个引用,最后将t返回出去了,循环结束。看看流程图。

此时t是谁??
通过第一次循环我们创建了一个没有数据的Node节点。此时t就是没有数据(但是有初始化数据)的Node。
Node是谁??
Node是通过传参的B线程经过封装的Node。

在这里插入图片描述

  • 总结 addWaiter(Node mode)方法做了什么?
    将当前线程B封装成了一个Node节点,通过自旋在队列head中生成了一个只有初始化数据的Node节点。
    1、将线程BNode节点通过prev指向了初始化节点
    2、再将tail通过CAS修改为线程BNode节点
    3、最后才是将初始化节点next指向了线程BNode节点。
    addWaiter(Node mode)方法结束。

拓展
此时线程C来了,分析下执行流程。

  • 看enq(final Node node)方法。获取到尾部节点赋值给变量t,即t为线程BNode节点,if不成立直接进入else块中。
    1、将线程CNode节点的prev指向了t(即线程BNode节点)
    2、通过CAS将尾部节点的引用指向线程CNode节点
    3、将t(即线程BNode节点)的next指向了线程CNode节点
    在这里插入图片描述

acquireQueued()方法

 final boolean acquireQueued(final Node node, int arg) {
        //定义变量为true
        boolean failed = true;
        try {
            //定义变量为false
            boolean interrupted = false;
            for (;;) {
                //当前节点是线程BNode节点
                
                //得到当前节点的prev的节点赋值给变量p
                final Node p = node.predecessor();
                //p是否为head   true
                //再次尝试去获取锁,此时线程A还继续在使用,所以获取不到 false
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //p为初始化节点
                //node是线程BNode节点
                //获取节点的waitStatus进行比较得到boolean值
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 分析acquireQueued做了什么?
    此时按照顺序读更容易理解。 有自旋。

1、传参的Node节点为线程BNode节点,arg为1。先获取到BNode节点的prev节点赋值给了变量p,(即初始化节点),进行了相关判断得知初始化节点是head节点。但是由于线程A占用了锁,导致线程B获取不到锁。从而进入shouldParkAfterFailedAcquire(p, node)方法。

3、逻辑与第一步相同,依旧进入shouldParkAfterFailedAcquire(p, node)方法。

5、进入parkAndCheckInterrupt()方法。逻辑就是将该线程进行休眠。
park()方法
禁止当前线程进行线程调度

  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取到waitStatus值
        int ws = pred.waitStatus;
        //Node.SIGNAL为  -1
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

2、获取到初始化节点的waitStatus值赋值给了变量ws值为0。
第一个判断 ws == -1?false
第二个判断 ws > 0 ? false
进入compareAndSetWaitStatus方法,将初始化节点Node的waitStatus值赋值为-1。return false方法结束。

4、获取到初始化节点的waitStatus值赋值给了变量ws值为-1。
第一个判断 ws == -1?true.
return true 方法结束。
对应的流程图
在这里插入图片描述

从lock开始到线程Node节点创建,以及双向链表的形成,到线程的的休眠流程就结束了。

unlock()方法

调用到AbstractQueuedSynchronizer中的release(int arg)方法

先分析tryRelease(int releases)方法。

再分析unparkSuccessor(Node node)方法。

按照上面思路加上流程图进行分析,安装序号阅读。

public final boolean release(int arg) {
        if (tryRelease(arg)) {
        //将头部节点赋值给变量h
            Node h = head;
            //h不为null并且h的waitStatus不为0
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  • 3、方法返回值为true进入if块中,将head赋值给了变量h,此时h为初始化节点(虚节点)。此时h.waitStatus的值在shouldParkAfterFailedAcquire(Node pred, Node node)中的compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法将waitStatus修改成了-1。此时的if判断是成立的,进入if块中分析unparkSuccessor(h)方法,记住此时h为初始化节点(虚节点)。
protected final boolean tryRelease(int releases) {//传参为1
            //获取到state 和 releases求减得到变量c
            int c = getState() - releases;
            //判断当前线程是否是不是工作区中的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //定义一个变量tree初始值为false
            boolean free = false;
            if (c == 0) {
                free = true;
                //设置工作区中的线程为null
                setExclusiveOwnerThread(null);
            }
            //将state值设置为变量c
            setState(c);
            return free;
        }
  • 1、A线程调用unlock() 方法进行解锁。会先进入tryRelease()方法,获取到state为1,传参releases为1,相减得到变量c,此时c为0。当前线程A和工作区中的线程是相同的不会发生异常。
  • 2、定义了一个free变量为false,进入if块中奖free设置成了true,并且将工作区中的线程设置为了null,此时工作区没有任何线程。最后将state的值修改为0,方法返回值为true。
private void unparkSuccessor(Node node) {
        //获取到node节点的waitStatus赋值给变量ws
        int ws = node.waitStatus;
        if (ws < 0)
        //通过CAS将当前Node的waitStatus值修改为0
            compareAndSetWaitStatus(node, ws, 0);
            //将Node的next指向变量s
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //把尾部节点赋值给变量t,如果变量t不为null并且不为当前Node节点,依次prev向前引用
            for (Node t = tail; t != null && t != node; t = t.prev)
             //t的waitStatus <= 0时,将t赋值给s
                if (t.waitStatus <= 0)
                    s = t;
        }
        //s不为null时,将s节点的线程进行唤醒
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 4、获取到初始化节点(虚节点)的waitStatus赋值给变量ws,ws此时为-1。
    满足第一个if块,通过CAS将当前Node的waitStatus值修改为0,获取到Node节点的next引用节点赋值给变量s,此时变量s即线程BNode节点。
    s此时是不为null的不会满足第二个if块。
    满足第三个if块。接着调用了LockSupport.unpark(s.thread)方法。该方法将s节点的线程进行唤醒(发送许可证)操作,即B线程即将开始工作。
  • 5、找到B线程休眠的方法
 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

判断该线程是否被中断过,我们安装没有中断的逻辑进行分析,得知在acquireQueued(final Node node, int arg)。看源码

final boolean acquireQueued(final Node node, int arg) {
        //定义变量为true
        boolean failed = true;
        try {
            //定义变量为false
            boolean interrupted = false;
            for (;;) {
                //当前节点是线程BNode节点
                
                //得到当前节点的prev的节点赋值给变量p
                final Node p = node.predecessor();
                //p是否为head   true
                //再次尝试去获取锁,此时线程A还继续在使用,所以获取不到 false
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //p为初始化节点
                //node是线程BNode节点
                //获取节点的waitStatus进行比较得到boolean值
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 6、开始分析返回false,if块不成立,进入下次循环。获取线程BNode的prev的引用赋值给变量p,此时变量p为初始化节点(虚节点)。
    if (p == head && tryAcquire(arg)) ,p是为head节点,进入tryAcquire方法。
    tryAcquire方法说过,放源码直接分析。

  • 8、返回值为true进入if块中,调用了setHead(Node node)做了什么?
    1、将线程BNode节点设置为头节点
    2、将Node节点的thread赋值为null
    3、将Node节点prev指向为null

将初始化(虚节点)节点的next引用为null,方便进行GC。

将free属性设置为false,我们按照线程没有中断的逻辑所以返回值为false,退出自旋。finally块中的判断不成立,方法结束。

此时的线程BNode节点就变成了我们初始化的节点(虚节点)。

final boolean nonfairTryAcquire(int acquires) {
            //获取到当前线程
            final Thread current = Thread.currentThread();
            //获取到同步状态
            int c = getState();
            if (c == 0) {  //表示当前的工作区可用即无任何线程
                //通过CAS将同步状态state修改为1
                if (compareAndSetState(0, acquires)) {
                    //将当前线程放入工作区中
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //获取到工作区中的线程和当前线程进行比较
            //可以看出ReentrantLock是可重入锁通过修改state表示同步次数
            else if (current == getExclusiveOwnerThread()) {
                //将state的同步表示加上传参的值
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //设置新的同步标识值    
                setState(nextc);
                return true;
            }
            return false;
        }
  • 7、获取到当前B线程,获取到同步状态,此时的state是为0的。if判断成立,进入if块,通过CAS将state修改为1,并且将线程B放入工作区中。方法返回值为true直接结束。

流程图
在这里插入图片描述

总结

到这就结束了,学习AQS对本人的帮助还是挺高的。慢慢也可以看得懂一点源码了,如有错误,欢迎指出。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值