AQS代码解析

本文深入解析了JavaAQS类的acquire和release方法,重点介绍了tryAcquire、addWaiter和acquireQueued等核心函数,探讨了公平锁与非公平锁的区别,以及如何处理线程同步和阻塞。
摘要由CSDN通过智能技术生成

简介

AQS全称是AbstractQueuedSynchronizer(抽象队列同步器).,是jdk juc包下的一个类,juc包下很多类都频繁用到了这个类的锁的思想和设计,我将结合ReentrantLock类并且按照非公平锁(公平锁和非公平的区别后面详细介绍)的方式来讲解AQS的设计思路和源码分析。接下来让我们一起看看吧!

Aqs

Aqs类主要有两个方法,分别是acquire以及release。在详细深入解读源码之前需要先介绍一下Aqs的工作原理,后续有不明白的点可以回过来先看看整体流程。先介绍一些基本概念:

  1. state:是一个使用volatile修饰的int类型变量,Aqs通过该变量来控制锁的状态,并发场景下通过volatile+cas 来保证原子性
  2. 同步等待队列:Aqs维护了一个双向链表,链表结构Node包含prev(前驱指针),next(后继指针),thread,waitStatus等属性.队列用于处理线程阻塞唤醒,争抢锁等事情。waitStatus包含的状态:
  • int CANCELLED = 1 (取消,该节点不需要再进行处理).
  • int SIGNAL = -1(唤醒,该节点等待唤醒,同时该节点.next可以进行安全阻塞).
  • int CONDITION = -2(条件队列,先暂时不讨论)
  • PROPAGATE = -3(传播级别,这个级别的线程可以进行唤醒)

acquire方法

public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 尝试获取锁,有公平/非公平两种方式
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // addWaiter: 把当前线程封装成Node加入到同步等待队列中.acquireQueued:对队列中的当前节点进行安全阻塞
            selfInterrupt();
    }

其中几个核心方法:tryAcquireaddWaiteracquireQueued
tryAcquire为尝试获取锁,addWaiter是把当前线程封装成节点加入到等待同步队列中,acquireQueued是尝试让队列首元素抢锁,并且抢失败后处理阻塞等事务.这里的逻辑是尝试抢锁,抢失败的话把当前线程封装成Node节点加入到同步等待队列中,并且对队列中元素尝试拿锁,失败后阻塞.

tryAcquire方法

获取到state,判断当前锁状态,如果state为0说明锁空闲,则当前线程可以抢锁,则通过cas设置state为1,如果成功了说明抢到了锁,ReentrantLock为独占锁,所以还会把独占线程exclusiveOwnerThread设置为当前线程。如果当前锁已经被抢了,但是当前独占现场为当前线程的话,说明当前线程正在进行锁重入的过程,那么同样cas设置state,只是state此时代表的是锁重入的次数

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) { // 通过cas设置state,抢锁
                    setExclusiveOwnerThread(current); // 独占式的锁有线程标记,抢到锁则标记为当前线程
                    return true;
                }
            }
            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;
        }

当tryAcquire方法返回失败,说明当前线程没有抢到锁,那么就准备进行阻塞相关的事宜了.
####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) { // 队列已经初始化过了就直接链接在后面
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node); // 初始化同步等待队列,初始化完后的结构:head->node(tail)
        return node;
    }

代码逻辑:把线程封装成Node加入到等待队列中.其中enq是首次进入时,会进行队列的初始化,而第一个if条件则是之后进入时会进到的.

enq方法

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node())) // cas设置head、tail
                    tail = head;
            } else { // 自旋后把node接在队列后面,tail也同步移动
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

这一段通过自旋保证队列的初始化一定成功.首次进入会通过cas设置队列头head和尾tail,第二次自旋则会把节点接到head后面,tail也移动到队列尾部.整体结构:head->Node(tail).(实际是双向链表,这里只是简单表示)

acquireQueued方法

参数node:当前线程通过addWaiter方法包装后得到的节点.
首先这里通过for循环不断的自旋,然后取到node的前驱节点p(node.prev),
条件p==head意思是当前节点node是head.next,即node为队首第一个节点,如果是队首节点则通过tryAcquire再次尝试抢锁.抢到锁的话当前节点进行出队操作,没抢到则进入shouldParkAfterFailedAcquire方法判断是否可以安全阻塞,如果可以安全阻塞则调用parkAndCheckInterrupt阻塞当前线程

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false; // 中断标识
            for (;;) {
                final Node p = node.predecessor(); // node.prev node的前驱节点
                if (p == head && tryAcquire(arg)) { // 如果node是队首节点,则尝试获取锁,如果成功获取到就出队
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted; // 返回当前线程的中断标识
                }
                if (shouldParkAfterFailedAcquire(p, node) && // 判断当前线程是否可以安全阻塞,如果可以安全阻塞则阻塞线程,返回中断标识
                    parkAndCheckInterrupt()) // 阻塞
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法

首先取到node.prev的等待状态waitStatus,后续的操作是基于状态来判断的.
1、如果node的前驱节点的状态为SIGNAL(-1),则表示当前节点可以安全阻塞,则返回true.
2、如果前驱节点状态大于0,则说明状态为CANCELLED(1),即取消,该节点则需要在队列中进行剔除处理
3、如果前驱节点状态为0,cas设置为SIGNAL(-1)

如果第一个线程进来前驱节点waitStatus为0,则先cas改成SIGNAL(-1),然后自旋再进入之后,前驱节点为SIGNAL的情况下,当前节点被视作可安全阻塞,返回true

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) // 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; // 剔除掉状态为CANCELED的节点,从后往前
            } 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); // 处理前驱节点状态,先暂时不考虑CONDITION状态,这里就处理0或者PROPAGATE状态,0和传播状态都可以唤醒
        }
        return false;
    }

parkAndCheckInterrupt方法

调用LockSupport.park来阻塞当前线程.

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

release方法

包含tryReleaseunparkSuccessor两个方法

public final boolean release(int arg) {
        if (tryRelease(arg)) { // 尝试释放锁
            Node h = head;
            if (h != null && h.waitStatus != 0) // 释放锁成功了则对同步等待队列进行唤醒.status应该要为Signal
                unparkSuccessor(h); // 唤醒队首节点或等待队列中其他有效节点(队首无效的情况下)
            return true;
        }
        return false;
    }

tryRelease方法

释放锁,通过对state的减操作来完成.如果state减到0了说明锁已经释放了,则返回true,并且设置独占现场为null

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) { // 如果state减为0,说明锁空闲了,则释放锁成功
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

unparkSuccessor方法

该方法进入的条件是成功释放锁,且head!=null,且head的状态为-1。
该方法逻辑是对node节点的状态进行判断.对CANCELD(1)状态的节点同样进行剔除,并且通过LockSupport.unpark(s.thread)唤醒队首节点(如果队首节点无效会在队列中继续找)

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0); // 把head的status改成0

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next; // 队首节点,如果无效后面会在队列里继续找符合的
        if (s == null || s.waitStatus > 0) { // 如果队首节点无效,即空或者为CANCELED状态,则剔除掉
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev) // 从后往前剔除,直到节点满足条件
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒当前节点
    }

如果有帮助到你可以点个赞哦~~
如果有什么不明白的可以在评论区留言,我会加以改正,你的评论可以帮助到更多人~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值