Java同步系列之AbstractQueuedSynchronizer源码分析2

文章参考:(4条消息) AQS源码探究_03 成员方法解析(加锁、资源竞争逻辑)_兴趣使然的草帽路飞-CSDN博客

该文章中有些错误,我进行了改正。

AQS成员方法解析

1.lock加锁方法

这里是ReentrantLock的公平锁的方法实现  在ReentrantLock的FairSync静态内部类中

    final void lock() {
        acquire(1);
    }

2.acquire令当前线程竞争资源的方法

    //位于AQS下的acquire
    public final void acquire(int arg) {
        //条件1 !tryAcquire(arg)  尝试去获取锁   获取成功返回true 取反为false后面的判断就不用再执行  获取失败的情况会返回false  取反会返回true 会接着执行后面的判断
        //addWaiter方法  将当前线程封装成node入队   如果线程尝试获取锁未获取到 就会将当前线程加入阻塞队列中  如果获取到锁就不会执行这个代码
        //入队列后调用acquireQueued()方法 (该方法包含挂起当前线程,以及线程唤醒后相关逻辑)
        //                 令当前线程不断的去竞争资源  直到成功才停止自旋
        // acquireQueued方法返回boolean类型 true:表示挂起过程中线程中断唤醒过  false:表示未被中断唤醒过
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

3.tryAcquire尝试获取锁的方法(公平锁方法实现)

    //位于ReentrantLock类的静态内部类FairSync中:是ReentrantLock中公平锁的tryAcquire实现
    //返回true  尝试获取锁成功  返回false  尝试获取锁失败
    protected final boolean tryAcquire(int acquires) {
        //获取到当前线程
        final Thread current = Thread.currentThread();
        // AQS中的state(加锁状态)值
        int c = getState();
        //如果条件成立,c==0  表示当前的AQS处于无锁的状态  无锁的情况下
        //该方法主要是尝试去获取锁  会判断队列中是否为空
        if (c == 0) {
            //因为此方法是公平锁的实现,任何时候都要检查一下当前线程之前,队列中是否有等待者
            //条件1 hasQueuedPredecessors()  判断FIFO队列中是否为空
            //true  ->表示当前线程前面有等待者线程  当前线程需要入队等待  取反的情况下为false后面的判断就不用执行
            //false ->表示当前线程前面没有等待者线程  直接可以尝试获取锁  取反为true 后面的判断会继续执行
            if (!hasQueuedPredecessors() &&
                //条件2:compareAndSetState(0,acquires)基于CAS去更新state的值
                //state更新成功:说明当前线程抢占锁成功
                //state更新失败,说明多个线程存在竞争,当前线程竞争失败,未能抢到锁的持有权
                compareAndSetState(0, acquires)) {
                //条件1和2都成立时,说明当前线程抢夺锁的持有权成功
                //抢占到锁后都需要做写什么呢?  设置当前线程为独占线程(锁的持有者)
                setExclusiveOwnerThread(current);
                //true  -> 当前线程尝试获取锁成功
                return true;
            }
        }
        //这里是c != 0的逻辑  说明当前有线程持有锁
        //这里是判断当前线程是否是持有锁的线程(独占线程) 因为ReentrantLock是可重入锁,获取锁的线程可以再次进入
        //如果条件成立  说明当前线程是独占锁的线程  要做些什么呢?这里是可重入的场景
        else if (current == getExclusiveOwnerThread()) {
            //获取当前线程的加锁状态 并累加
            int nextc = c + acquires;
            //这里是越界判断,当重入的深度很深超过int值的Max之后,再次+1会变成负数
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
                //更新加锁状态
                setState(nextc);
                //true ->当前线程尝试获取锁成功
                return true;
        }
            //false   尝试获取锁失败的场景
            //第一种情况是:CAS加锁失败  且当前线程前面有等待的线程
            //第二种情况是:state > 0 且当前线程不是占用锁的线程
            return false;
   }

4.addWaiter将当前线程添加到阻塞队列的方法

    //位于AQS下:将当前线程添加到阻塞队列的方法
    //最终返回包装当前线程的node
    private Node addWaiter(Node mode) {
        //构建Node  把当前线程封装到Node中  mode:Node节点的模式,例如Node.EXCLUSIVE  当前节点是独占模式
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //线程快速入队的方式
        //先获取队尾节点  保存到pred
        Node pred = tail;
        //这里判断队尾节点是否为空  条件成立的话  说明队列中已经有node了
        if (pred != null) {  
            //令当前节点node的前驱指向pred      1.第一次绑定 将当前节点的node的prev指向pred
            node.prev = pred;                  2.通过CAS更新队尾tail
            //基于CAS更新队尾tail           3.第二次绑定  将pred的next指向node  完成双向绑定 
            if (compareAndSetTail(pred, node)) {
                //tail更新成功后:设置pred的next指向node  这时完成双向绑定
                pred.next = node;
                //返回node
                return node;
            }
        }
        //线程完整入队方式(自旋入队)
        //执行到这里有以下两种情况
        //1.tail == null 当前队列是空队列
        //2.cas设置当前newNode为tail时失败了,被其他线程抢先一步
        //这里是自旋入队的逻辑  只有入队成功才结束自旋
        enq(node);
        //返回node
        return node;
    }

5.enq当前线程完整入队的方法(自旋入队)

    //位于AQS中将当前线程完整入队的方法
    private Node enq(final Node node) {
        //这里是自旋的逻辑   只有封装当前线程node入队成功  才会跳出循环
        for (;;) {
            //先获取到尾结点
            Node t = tail;
            //第一种情况:空队列 -> 当前线程是第一个抢占锁失败的线程
            //当前持有锁的线程(tryAcquire方法直接获取到锁的线程,在该方法逻辑中,并没有将持有锁的线程入队) 
            //而按理说阻塞队列的head结点就应该是当前持有锁的线程才对,并没有设置过任何node,
            //所以作为该线程的第一个后驱next,需要给持锁线程补一个node结点并设置为阻塞队列的head
            //head结点任何时候  都代表当前占用锁的线程
            if (t == null) { // Must initialize   需要初始化一个head
            //如果compareAndSetHead条件成立:说明当前线程给当前持有锁的线程,补充head操作成功
                if (compareAndSetHead(new Node()))
                    //表示队列中只有一个元素,这里就表名当前持有锁的线程被放入阻塞队列且为head了
                    tail = head;
                   //注意:这里并没有返回  还会继续自旋  下次再进入循环时阻塞队列中已经有节点了,且head为持有锁的节点
            } else {
                //队列中已经有元素了  这里是在队尾追加node的逻辑
                
                //如何入队列呢?和addWaiter方法入队逻辑一样
                //1.找到队尾节点t
                //2.将newNode的prev指向t
                //3.CAS更新tail为neeNode
                //4.更新t的next指向newNode
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //注意:入队成功  一定要return终止无限for循环
                    //返回此时的队尾节点
                    return t;
                }
            }
        }
    }

6.acquireQueued真正去竞争资源的方法

acquireQueued需要做什么呢?

  • 1.当前节点如果没有被park挂起,则 ===> 挂起当前线程。
  • 2.线程唤醒后 ===> 需要做一些线程唤醒之后的逻辑。
    //位于AQS中:真正去竞争资源的方法
    //参数final Node node:封装当前线程的node,通过上述的分析我们知道该node已经入队成功了
    //参数arg :当前线程抢占资源成功后,更新state值是需要用到
    //返回true:表示挂起过程中线程中断唤醒过,返回false  表示未被中断唤醒过
    final boolean acquireQueued(final Node node, int arg) {
        // true:表示当前线程抢占锁不成功  失败了 需要执行出队逻辑
        // false:表示当前线程抢占锁成功  
        boolean failed = true;
        try {
            // 当前线程是否被中断
            boolean interrupted = false;
            // 自旋操作
            for (;;) {
                //什么情况下会执行到这里
                //1.进入for循环时,在线程尚未被park前会执行
                //2.线程park后,被唤醒之后也会执行

                //获取当前节点的前驱节点
                final Node p = node.predecessor();
                //p == head条件成立时:说明当前node为head节点的后继节点 head.next指向node 有权利去争夺锁
                //tryAcquire(arg) 尝试去获取锁,如果条件成立,说明head对应的线程已经释放了锁,而作为head的后继节点的线程,刚好可以获取锁
                //tryAcquire 如果条件不成立:说明head对应的线程尚未释放锁,而作为head的后继节点的线程,这时候仍需要继续park挂起
                if (p == head && tryAcquire(arg)) {
                    //运行到这里  表示两个条件都满足  拿到锁
                    //设置封装当前线程的节点为head节点(head无论什么时候都是持锁线程的节点)
                    setHead(node);
                    //到这里来我们知道p节点为传入参数node节点的前驱节点  p节点为head  进行了setHead操作后 此时的head是node节点  p.next设置为null 是指P已经是垃圾了需要回收  没有引用指向P
                    p.next = null; // help GC
                    //当前线程抢占锁没有失败  failed = false
                    failed = false;
                    //此时这个逻辑是当前node抢占到了锁  未被中断过就返回false
                    return interrupted;
                }
                //shouldParkAfterFailedAcquire:判断当前线程获取锁资源失败后,是否需要挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //parkAndCheckInterrupt:挂起当前线程,并在该线程唤醒之后,返回当前线程的interrupted中断标记
                    //1.正常唤醒:其他线程调用unpark方法,唤醒该线程
                    //2.其他线程给当前挂起的线程一个中断信号(中断挂起)                    
                    pakAndCheckInterrupt())
                    //interrupted=true表示当前node对应的线程是被中断信号唤醒的
                    interrupted = true;
            }
        } finally {
            //failed为true时  表示当前线程抢占锁不成功
            if (failed)
                //取消node节点的线程资源竞争
                cancelAcquire(node);
        }
    }

7.shouldParkAfterFailedAcquire方法

    //位于AQS中:判断当前线程获取锁资源失败后,是否需要挂起
    //true: 需要挂起   false:不需要挂起
    //参数1:Node pred 当前线程的前驱节点
    //参数2:Node node 封装当前线程的节点
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前驱节点的waitStatus
        //0:默认状态 | -1:Singnal状态(表示当前节点释放锁后会唤醒它的第一个后继节点)
        //waitStatus>0 表示当前节点是CANCELED状态
        int ws = pred.waitStatus;
        //如果条件成立  则表示前驱节点是可以唤醒当前线程节点的节点
        if (ws == Node.SIGNAL)
            //返回true后,在acquireQueue方法中会继续调用parkAndCheckInterrupt方法去park当前线程节点     
            //注意:一般情况下,第一次来到shouldParkAfterFailedAcquire方法中时,ws不会是-1
            return true;
        //如果ws>0成立 表示当前节点是CANCALED状态
        if (ws > 0) {
            //该循环是一个找pred.waitStatus>0 的前驱节点的过程
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //找到符合条件的前驱节点后,令其下一个节点为当前线程的node
            //隐含着一种操作  即CANCELED状态的节点会被出队
            pred.next = node;
        } else {
            //当前node前驱节点的状态就是0,即默认状态这种情况
            //将当前线程node的前驱节点的状态,强制设置为SIGNAL,表示该节点释放锁后会唤醒它的第一个后继节点。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }


    //位于AQS中  挂起当前线程节点
    private final boolean parkAndCheckInterrupt() {
        //线程挂起
        LockSupport.park(this);
        return Thread.interrupted();
    }

Stkcd [股票代码] ShortName [股票简称] Accper [统计截止日期] Typrep [报表类型编码] Indcd [行业代码] Indnme [行业名称] Source [公告来源] F060101B [净利润现金净含量] F060101C [净利润现金净含量TTM] F060201B [营业收入现金含量] F060201C [营业收入现金含量TTM] F060301B [营业收入现金净含量] F060301C [营业收入现金净含量TTM] F060401B [营业利润现金净含量] F060401C [营业利润现金净含量TTM] F060901B [筹资活动债权人现金净流量] F060901C [筹资活动债权人现金净流量TTM] F061001B [筹资活动股东现金净流量] F061001C [筹资活动股东现金净流量TTM] F061201B [折旧摊销] F061201C [折旧摊销TTM] F061301B [公司现金流1] F061302B [公司现金流2] F061301C [公司现金流TTM1] F061302C [公司现金流TTM2] F061401B [股权现金流1] F061402B [股权现金流2] F061401C [股权现金流TTM1] F061402C [股权现金流TTM2] F061501B [公司自由现金流(原有)] F061601B [股权自由现金流(原有)] F061701B [全部现金回收率] F061801B [营运指数] F061901B [资本支出与折旧摊销比] F062001B [现金适合比率] F062101B [现金再投资比率] F062201B [现金满足投资比率] F062301B [股权自由现金流] F062401B [企业自由现金流] Indcd1 [行业代码1] Indnme1 [行业名称1] 季度数据,所有沪深北上市公司的 分别包含excel、dta数据文件格式及其说明,便于不同软件工具对数据的分析应用 数据来源:基于上市公司年报及公告数据整理,或相关证券交易所、各部委、省、市数据 数据范围:基于沪深北证上市公司 A股(主板、中小企业板、创业板、科创板等)数据整理计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值