AQS概述


前言

在多线程下,如果被请求的共享资源空闲,则将当前请求资源的线程设为有效的工作线程,并将共享资源设为锁定状态,如果被请求的共享资源被占用,那么就需要一套阻塞等待以及被唤醒时锁分配的机制,这就是AQS的核心思想


一、AQS是什么?

AQS 是 Java 并发包中的一个关键组件,用于构建锁和其他同步组件的基础框架。
它提供了一种实现同步器(如锁和其他同步工具)的框架,被许多同步类所依赖,例如 ReentrantLock、Semaphore 等。
AQS 的核心思想是基于 FIFO 队列 实现线程的阻塞和唤醒,以及维护同步状态。

二、AQS的重要成员变量

1.源码检视

//用于构造fifo队列的node内部类
static final class Node
//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
//同步状态,后面会详细讲解
private volatile int state;
//旋转速度快于使用定时停留的纳秒数。粗略的估计足以以非常短的超时来提高响应性。
static final long spinForTimeoutThreshold = 1000L;
//用来调用jvm的api的unsafe类
private static final Unsafe unsafe = Unsafe.getUnsafe();

2.state

AQS主要维护了一个表示锁状态的变量。它表示有多线程获取了锁

  • 通过CAS操作实现队state的修改

3.等待队列

AQS维护了一个可阻塞的等待队列,也叫CLH队列

  • 队列的头节点和尾节点分别表示对头和队尾
  • CLH队列由链表实现,以自选的方式获取资源,是可阻塞的先进先出的双向队列

三、获取资源与释放资源

获取资源的方法有acquire()和aquiredShared()
acquire() 用于独占模式获取资源,它会自旋直到成功获取资源或被中断。
addWaiter() 将当前线程包装成一个节点并插入队尾,返回在等待队列中的节点。

    private boolean doAcquireNanos(long arg, long nanosTimeout)
            throws InterruptedException {
        //判断设置的超时时间是否正确
        //如果不正确,直接返回false
        //那么上一层的tryAcquirenano方法整体就会返回false
        //那么就不可以继续执行下去了
        if (nanosTimeout <= 0L)
            return false;
        //计算获取锁的限制时间
        //超过这个时间就不可以获取了
        //System.nanoTime是一个本地方法,用来获取虚拟机时间的,精确到纳秒级别
        //所以传进来的nanosTimeout必须为纳秒级别
        // 1秒 = 10^9纳秒,十亿份之一
        //这也是为什么使用long类型
        final long deadline = System.nanoTime() + nanosTimeout;
        //因为没抢到锁,所以要将该线程加入到队列里面
        //并且nextWaiter为Exclusive,代表该结点线程状态是独占式的
        final Node node = addWaiter(Node.EXCLUSIVE);
        //failed变量记录过程是否出错
        boolean failed = true;
        try {
            //死循环
            for (;;) {
                //下面这段代码就是自旋获取锁的逻辑
                //获取上一个线程
                final Node p = node.predecessor();
                //从下面这个判断可以判断出,这个抢锁过程是公平的
                //如果上一个线程是头结点
                //并且自己尝试获取锁成功
                if (p == head && tryAcquire(arg)) {
                    //那么该线程就可以继续运行了
                    //先将头结点设为自己
                    setHead(node);
                    //断开与头结点的连接
                    //让头结点可以回收
                    p.next = null; // help GC
                    //failed为false代表自旋过程无出错
                    failed = false;
                    //自旋结束,返回true,回到上一层继续运行
                    return true;
                }
                //上一个不是头结点,或者虽然是头结点但抢不到锁
                //计算还需要等待多长时间
                nanosTimeout = deadline - System.nanoTime();
                //如果没有时间剩余了
                if (nanosTimeout <= 0L)
                    //返回false
                    //上一层处理
                    return false;
                //还有时间剩余,看需不需要park
                //这里一样是自旋两次,然后就会返回true,代表需要Park
                //继续判断剩余时间是否足够进行park
                //因为如果park需要的时间都要多于线程剩下的时间
                //没什么必要park了,让其再自旋多一次,拿不到就超时返回false
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    //如果超时了,而且需要park,那就park掉
                    //这里park掉,线程也是会继续进行倒计时的
                    //在这段时间内都会被park掉,这段时间内,只有unpark和interrupt可以恢复
                    //超过这个时间也会恢复,然后又一次自旋,然后超时GG
                    LockSupport.parkNanos(this, nanosTimeout);
                //判断唤醒该线程的方式是不是被别的线程interrupt
    			//因为park是不会覆盖interrupt标志位的
                if (Thread.interrupted())
                    //如果是被中止
                    //抛出异常
                    throw new InterruptedException();
            }
        } finally {
            //同理,没有修改过failed变量,抛出异常,超时
            //就会执行下面cancelAcquire
            if (failed)
                cancelAcquire(node);
        }
    }

释放资源:

private void cancelAcquire(Node node) {
        
    	//判断要移除的线程在不在
        if (node == null)
            //不在就直接返回
            return;
		//将结点里面的线程删除
        node.thread = null;

        // Skip cancelled predecessors
    	//获取结点的上一个结点
    	//下面就是针对前面的一些被取消的结点的操作
    	//这些结点也是要被删除的
        Node pred = node.prev;
    	//循环判断上一个结点的状态是否大于0,如果大于0代表就要被删除
        while (pred.waitStatus > 0)
            //进入到这里就判断上一个结点要删除了
            //所以让Node与上上个结点连接,
            //同时将pred变为上上个结点,为了下一次循环
            //这一步可以视为两步
            //pred = pred.prev
            //node.prev = pred
            node.prev = pred = pred.prev;

    	
    	//循环在这里就结束了,当初就是在这里卡了好久
    	//现在已经让正常的结点node.prev为正常的结点
    	//但还没有进行断开,下面的操作就是断开的
    	//pred此时就是正常的结点
    	//predNext就是前面一个被取消的结点
    	//下面只需要对这个predNext删除就行,因为这段的线程都是取消的
        Node predNext = pred.next;

		//将线程状态改为cancelled
        node.waitStatus = Node.CANCELLED;

       //如果线程是尾结点
    	//那么就CAS将尾结点换成Pred,也就是此时尾结点变成了pred
        if (node == tail && compareAndSetTail(node, pred)) {
            //然后CAS将pred后面的线程全部删除,改为Null
            //这里就完全断开了需要删除的结点,并且正常的结点成为了尾结点
            compareAndSetNext(pred, predNext, null);
        }
    	//下面这一步,针对node不是尾结点
    	//或者node是尾结点,但在替换过程中,新插入了结点
    	else {

            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)
                    //让正常结点指向node的后驱结点
                    //即断开了node
                    compareAndSetNext(pred, predNext, next);
            }
            //如果是正常结点是头结点,
            //那就直接唤醒后面的线程
            else {
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值