AQS临界场景分析

一 背景

之前在b站看了些AQS的视频。在看的过程中,总是在想:当遇到一些比较临界的场景时。AQS时如何保证并发安全的,于是就对这些场景进行模拟。通过debug来搞清楚逻辑,

在实验前,有几点说明:

  • 之后的代码时基于jdk8公平锁(我觉得两个锁的区别不是很大`)
  • 我的理解中,并发编程时宏观上并行,微观上串行(补充:后来查阅到java在多核cpu中可以并行执行!!!再次对下面的并发场景进行分析,发现代码依旧安全.所以串行执行这个前提可以去掉!
  • AQS中对一些关键字段(用来判断状态)作了volatile修饰,保证了只要数据被修改。就对所有的线程可见
  • AQS中的阻塞是通过LockSupport这个类来实现的,并且如果先unpark某个线程,之后该线程park。改线程不会阻塞
  • 当线程较多时,AQS会使用队列来存储一个个阻塞的线程。前一个线程会唤醒下一个(第一个排队)的线程
  • 队列的第一个节点不存储阻塞的线程。其thread字段为null。我的理解时该Node代表一个可能正在执行的线程

二 如果前一个线程先unpark会怎么样

上面提过了,先unpark,在park。线程不会阻塞。不会出现无法唤醒的场景

三 如果前一个线程后unpark会怎么样

这应该是比较普遍的情况。这样前一个线程释放锁后,后一个线程就会被唤醒,然后往下执行(就是拿到锁了

四 就是后一个线程刚加入队列,但前一个线程已经释放锁

情况一

因为AQS中做了许多次判断
在这里插入图片描述

情况二

那也有可能是在这一步判断之后,前一个线程才释放锁。那我们继续跟着代码往下走

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)) {//for循环第一次会为true
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//主要就是一步
                    parkAndCheckInterrupt())//这一步就是park
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

我们进入shouldParkAfterFailedAcquire方法
第一次进来时pred的waitStatus肯定为0.因为前一个线程对应Node的status就是后面一个线程设置的,该线程第一次进来。肯定还是默认值0.

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) {
            /*
             * 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);//第一次代码进入这一步
        }
        return false;//因为是false。还会走外面的for循环
    }

注意该场景的前提是前一个线程此时已经unpark了。那么该锁就是空的状态。而且字段都是volatile的,所以当前线程可见

再次判断。下面条件肯定成立,当前线程不阻塞。相当于获取到了锁,继续往下执行

if (p == head && tryAcquire(arg)) {

情况三 park前一步时间片用完,unpark结束或跳过

线程A先执行到这一步
在这里插入图片描述
另一个线程B开始继续执行
在这里插入图片描述
说实话,如果真出现了这种场景。那么确实第一个线程会一直阻塞。现在我们需要分析。这种场景到底会不会出现

我们先分析线程B执行到这一步(return ture前面之前的几种情况,
也就是这一步的情况
在这里插入图片描述

4.1 h=null

那说明B线程还没走到下面这一步(这次声明:微观串行+可见性!!!是可以得出下面这个结论的)无法想到反例
在这里插入图片描述
那么之后B线程必然会在下面一步退出,那么线程A就不可能到park上面这一步。所以该情况不成立!

if (p == head && tryAcquire(arg)) 

4.2 h!=null &&waitStatus =0

此时场景就回到了情况二(不是情况二,但很像!情况二是更后面的状态,明确释放了锁.)(注意:释放锁,并不代表一定要upark。我这里是指线程退出这个unlock方法),

waitStatus=0说明什么?
说明线程A还没有执行过下面这一步
在这里插入图片描述
但当B线程这一步时,B线程实际已经修改过state了
在这里插入图片描述

那么线程A必然会在后面通过if条件实现获取锁,也就不会走到park那一步了

if (p == head && tryAcquire(arg)) 

4.3 h!=null &&waitStatus !=0

此时线程B会执行Unpark(B线程)。那么无论如何,B线程都不可能锁死.

继续扩展一下
那有没有可能unpark了线程A,但A线程却没有park呢?

可以确定的说: 可以! 因为我在本地通过debug模拟了这种情况,可以发生。

五 总结

acquireQueued方法里的for循环,多次判断有点精髓。总感觉自己有点悟,但抓不到重点。

但还是记录一下

  1. trylock时为什么先修改state?如果将tryRelease方法放后面执行,那么肯定会出现一直阻塞的场景。可以想象一下情况4.1
  2. 如何保证线程阻塞了,那么一定会在某个时刻被唤醒。其实就是上面几种场景的分析。

核心思想

就三句话

  1. 规定线程park前,需要将前一个node的waitStatus设置为其他值(非零)
  2. 如果明确有后任节点(waitStatus!=0时,这个后任节点就是第一个排队的节点,位置是第二个),那就不管三七二十一,unpark这个节点的线程(不管它是不是用的到
  3. 如果前一个线程没有执行unpark了(已成事实),为了保证不出现问题。只能在后任节点线程park前多做几次检查。因为没有执行unpark(推断出当时waitStatus=0),并且肯定已经设置state=0了,那么后任节点只要在修改waitStatus前(也就是park前检查state即可
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值