AQS内容详解

AQS的概览

  1. Lock的AQS流程
    1. AQS主要流程有哪些
      1.自旋锁
      2.队列
      3.CAS
    2. CAS原理
      当一个线程要更新一个成员变量的值时,首先,该线程会从内存中复制一个变量副本到线程的栈帧中(X=0),此时要将变量X进行自增操作(X=X+1),此时在线程栈帧中实际上会产生一个临时变量update=X+1。在刷新回内存时,会首先拿着变量X和主内存中的变量X进行比较,如果线程栈中的变量值等于主内存个中的变量值,那么就把自增之后的变量update刷新到主内存中去。当第二个线程再刷新时,会出现线程栈中的变量值和主内存中的变量值不一致的情况(此时主内存中的变量已经成为上一次修改后的变量1,对比失败),如果对比失败,则直接返回,重新获取数据进行操作。
    3. 自旋锁
      当线程没有获取到锁的时候,会进行自旋等待,一直到获取到锁之后跳出
    4. 队列
      没有获取到锁的线程会在队列中等待被唤醒去争夺锁的控制权
    5. LockSupport
      如果没有获取到锁的线程始终在自旋获取锁,那么就会一致占用CPU的资源,极大的浪费资源,所以需要让线程释放锁的资源。LockSupport.lock(),和LockSupport.unlock()两个方法可以很好的解决这个问题,在获取锁失败的时候执行lock()方法,再获取到锁的线程释放锁的时候执行unlock方法唤醒后面的锁。

AQS的重要方法

AQS的重要参数

waitStatus参数:
SIGNAL:等待唤醒
CANCELLED:当前节点已经废弃
CONDITION:条件队列
PROPAGATE:广播

  1. ReentrantLock类并没有继承AQS类,他是利用一个Sync内部类继承的AQS类。然后再利用FiarSync和UnfairSync来实现这个Sync内部类,定义具体的方法。
  2. Lock.lock() 方法详解
    Lock.lock()方法里面调用了acquire(int i)方法,然后acquire方法里面会调用tryAcquire方法(尝试获取锁)
		protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); // 获取当前执行的线程
            int c = getState(); // 获取当前要获取锁的节点的状态,入锁几次
            if (c == 0) { // 判断当前要获取锁的节点是否是刚刚创建出来的
                if (!hasQueuedPredecessors() && // 判断当前节点是否是队列中的唯一节点,即队列初始化的节点
                    compareAndSetState(0, acquires)) { // 如果上面的不成立,即队列中存在多个节点,则CAS执行状态变更,将状态变更为1
                    // 这个地方就是判断等待队列中是否存在线程节点,如果等待队列中不存在线程节点,则说明当前等待队列是个空的,直接设置状态为废弃就可以了
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 如果当前进来的节点是当前获取锁的节点
                int nextc = c + acquires; // 直接将锁的次数+1,此处即重入的逻辑
                if (nextc < 0)// 如果此时nextc还小于0,则说明status小于-1,但是锁状态最小为0
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);// 设置锁的状态,入锁几次
                return true;
            }
            return false;
        }
	尝试获取锁失败之后,会执行addWaiter()方法(加入队列的方法)
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) { //  判断尾巴节点是否为null,如果不是则说明队列中有节点元素
            node.prev = pred; // 将当前线程节点的前指针指向原本的尾巴节点
            if (compareAndSetTail(pred, node)) { // CAS的方式设置当前线程节点到尾巴节点,因为可能会存在多个线程争夺设置尾巴的资格,所以如果不用CAS保证原子性,则会出现刚设置一个尾巴,然后下一个线程就将尾巴替换了的情况,所以要用CAS保证原子性
                pred.next = node; // 设置原本的尾巴,即刚才设置的上一个节点的下一个节点为当前节点,形成双向链表
                return node; // 将当前线程的node返回
            }
        }
        // 如果尾巴不是null,则说明原本的等待队列中是存在值的
        enq(node); // 初始化队列并且将当前节点放到队列尾部
        return 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)) { // 再次CAS将当前线程节点设置为尾巴节点
                    t.next = node; // 设置原本的尾巴节点的下一个节点为当前节点,形成双向链表
                    return t;
                }
            }
        }
    }

加入队列后,会再执行acquireQueued方法。这个方法不会有多个线程同时进来,因为前面是CAS的方式把节点加入到尾部

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false; // 当前线程是否有中断信号
            for (;;) {
                final Node p = node.predecessor(); // 将当前线程的前一个线程获取到
                if (p == head && tryAcquire(arg)) { // 判断前一个节点是否为头节点,因为是独占模式,所以只有头节点后面的一个节点可以获取锁,如果前一个节点是头节点,那么会再次对当前节点获取锁,如果执行下面的节点入队后,会再执行一次这个判断,即如果入队的节点是头结点后面的第一个节点,那么会再次获取一次锁,因为线程放弃CPU后会从激发态转为用户态,会比较耗资源
                    setHead(node); // 如果前一个节点是头节点,那么说明当前节点可以唤醒,将头节点设置为当前节点,将该节点中的线程和前指针都制空,因为此时线程已经获取到锁了,不在需要记录在节点中
                    p.next = null; // help GC // 断开老的头结点的next指针,彻底断开老的头节点
                    failed = false; 
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&  // 修改当前节点的前一个节点的状态,并且将废弃的节点删除掉,如果原本的状态不是可唤醒,返回的是个false,如果原本的状态已经是可唤醒了,则返回true
                    parkAndCheckInterrupt()) // 执行LockSupport.park()并且消除中断信号
                    interrupted = true;
            }
        } finally {
            if (failed) // 这里只有异常退出的时候才会将当前节点设置为可删除状态
                cancelAcquire(node);
        }
    }
    
    
    
    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.
             */
            return true; // 返回唤醒
        if (ws > 0) { // 如果状态大于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); // CAS的方式设置节点状态为可唤醒状态
        }
        return false; // 返回false
    }
    
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt(); // 上面的acquireQueued里面如果是线程有中断信号的,会将线程的中断信号清除,在这里再补回来
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值