波吉学源码——AQS源码剖析

在了解AQS之前我们需要来认识一下这个工具类,因为该工具类是创建锁和其他同步类的基础

LockSupport

该工具类的主要作用是挂起和唤醒线程

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的,LockSupport是使用Unsafe类实现的,下面介绍几个重要的函数

void park()

public static void park() {
    UNSAFE.park(false, 0L);
}

如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,如果没有拿到LockSupport关联的许可证,则调用线程会被禁止参与线程的调度,会被阻塞挂起

在其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,调用part()方法而被阻塞的方法会返回,另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者被虚假唤醒,则阻塞线程也会返回。

void unpark(Thread thread)

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

当一个线程调用unpark()时,如果参数thread线程没有持有thread与LockSupport关联的许可证,则让thread持有,如果thread之前因调用park而被挂起,则调用unpark后,该线程会被唤醒,如果thread之前没有调用park,则unpark方法后,再使用park会立即返回

void parkNanos(long nanos)

与park方法类似,但该方法的不同之处在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后自动返回

public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos);
}

void park(Object blocker)

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

Thread类里面有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的成员变量里,使用带有blocker参数的park方法,线程堆栈可以提供更多有关阻塞对象的信息

抽象同步队列AQS

AbstractQueuedSynchronizer它是实现同步器的基础组件,并发包中锁的底层就是AQS实现的

在这里插入图片描述

AQS属性

由图可知,AQS是一个FIFO的双向队列,其内部通过head和tail记录队首和队尾元素,队列元素的类型为Node。在AQS中维持了一个单一的状态信息state可以通过getState,setState等修改其值,对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于semaphore来说,state用来表示当前可用信号的个数,对于CountDownlatch来说,state用来表示计数器当前的值

其中Node中的thread变量用来存放进入AQS队列里面的线程Node结点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,EXCLUSIVE用来标记线程是获取独占资源时被挂起放入AQS队列的,waitStatus记录当前线程等待状态,可以为CANCELLED(线程取消)、SIGNAL(线程唤醒)、CONDITION(线程在条件队列里等待)、PROPAGATE(释放共享资源时需要通知其他结点),prev记录当前结点的前驱结点,next记录当前结点的后继结点

AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列,ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量await方法后被阻塞的线程

AQS线程同步

对于AQS来说,线程同步的关键是对状态值state进行操作根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。在独占方式下获取和释放资源使用的方法为:void acquire(int arg) boolean release(int arg)

在共享方式下获取和释放资源的方法为void acquireShared(int arg) void releaseShared(int arg)

在独占式后去资源是与线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程操作state会发现当前资源不是自己持有,就会失败阻塞

对应共享方式的资源与线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源以后,另外一个线程再次去获取如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获锁即可

在独占方式下,获取和释放资源的流程如下:

  1. 当一个线程调用acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型Node.EXCLUSIVE的结点插入到AQS阻塞队列的尾部,并调LockSupport.park(this)挂起自己

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
  2. 当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,这里设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面的一个线程,被激活的线程尝试tryAcquire尝试,看当前状态变量的值是否能够满足自己的需要,满足该线程被激活,然后继续向下运行,否则还是会被AQS队列挂起

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,这两个方法需要具体的子类去实现,这里体现了模板方法的设计模式思想

在共享方式下,获取和释放资源的流程如下:

  1. 当一个线程调用acquireShared(int arg)方法获取共享资源时,会首先使用tryAcquireShared方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型Node.SHARED的结点插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)挂起自己

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    
  2. 当一个线程调用releaseShared(int arg)方法时会尝试使用tryReleaseShared操作释放资源,这里设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面的一个线程,被激活的线程尝试tryReleaseShared尝试,看当前状态变量的值是否能够满足自己的需要,满足该线程被激活,然后继续向下运行,否则还是会被AQS队列挂起

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

需要注意的是,AQS类并没有提供可用的tryAcquireShared和tryReleaseShared方法,这两个方法需要具体的子类去实现

AQS入队操作

当一个线程获取锁失败后该线程会被转换为Node结点,然后会调用下面方法插入到AQS队列中

private Node enq(final Node node) {
    for (;;) {
        //1.指向尾结点
        Node t = tail;
        //2.第一次循环:如果t为空cas操作给头结点赋值,然后头结点赋值尾结点,当队列为空时
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //3.第二次循环:将待插入结点的前结点赋值为t
            node.prev = t;
            //4.cas操作给尾结点赋值为node
            if (compareAndSetTail(t, node)) {
                //5.cas操作成功则将t结点的next结点赋值给node
                t.next = node;
                return t;
            }
        }
    }
}

在这里插入图片描述

AQS条件变量的支持

lock.newCondition()的作用起始时new了一个在AQS内部声明的ConditionObject对象,ConditionObject时AQS的内部类,可以访问AQS内部的变量和方法,每个条件变量内部都维护了一个条件队列,用来存放调用条件变量的await()方法时阻塞的线程。,注意时条件队列和AQS的队列不是一回事

在这里插入图片描述

当线程调用条件变量的await()方法时,(调用await()方法的前提是获得锁)在内部会创建一个类型为Node.CONDITION的node结点,然后将该结点插入到条件队列末尾,之后当前线程会释放获取的锁也就是操作锁对应的state变量,并被阻塞挂起

一句话总结,await会释放锁并把当前线程阻塞挂起,在该条件队列中会插入一个node结点,但释放锁的前提是占有锁

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //创建node结点并插入到条件队列末尾
    Node node = addConditionWaiter();
    //释放当前线程获取的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        //挂起当前线程
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

当线程A因为await方法阻塞时,线程B调用条件变量的signal方法时,在内部会把条件队列里面队头的一个线程节点从条件队列里面移除并放入AQS的阻塞队列里面,然后激活这个线程

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

存量美团骑手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值