AQS之hasQueuedPredecessors方法源码分析

JUC-AQS原理篇
JUC-AQS源码篇
JUC-AQS的Condition之await和signal源码解析

JUC-CountDownLatch基础篇
JUC-CountDownLatch源码分析
JUC-Semaphore基础篇
JUC-Semaphore源码分析
JUC-ReentrantReadWriteLock锁基础篇
JUC-ReentrantReadWriteLock锁源码分析
JUC-ReentrantLock锁基础篇
JUC-ReentrantLock锁源码分析
JUC-CyclicBarrier基础篇
JUC-CyclicBarrier源码分析


hasQueuedPredecessors方法的作用是判断当前线程需不需要排队,根据同步队列中是否已经有其他的线程在排队的情况来决定,如果有其他的线程在排队,就需要排队,没有,就不需要排队。此方法返回true,代表当前线程需要排队,返回false,表示当前线程不用排队。
hasQueuedPredecessors源码:

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

1.为什么先读取tail,再读取head

主要是为了防止后面的代码s = h.next抛出空指针异常,即是为了防止h为null的情况发生。
理由:
我们知道想要执行s = h.next这段代码,必须先满足h != t这个条件。
那么h != t的h,t取值情况有:

  • h为null,t不为null
  • h不为null,t为null
  • h,t都不为null并且不相等
    我们知道h,t为null的情况只能发生在同步队列进行初始化的时候,即调用enq方法的时候:
    在这里插入图片描述
    由于进行同步队列初始化的四行代码并不是一个原子操作,它们随时有可能执行到某一段代码就让出cpu,让其他的线程执行了。
    通过上面的代码我们可以得知,同步队列初始化时,是先给head赋值,再给tail赋值。那么我们可以得知,如果tail为null的话,head可能是null,也可能不是null(这个就是已经执行了compareAndSetHead(new Node())方法给head赋值,还没来得及执行tail = head代码给tail赋值)。如果tail不为null的话,那么head一定不为null的。
    那么在hasQueuedPredecessors方法中,如果先执行Node h = head,再执行Node t = tail的话可能会存在的情况有:
  • 执行Node h = head时同步队列已经完成了初始化,h,tail都不为null
  • 执行Node h = head时,同步队列还没有进行初始化或者正在进行初始化但是compareAndSetHead(new Node())这一步还没有完成,h为null,当执行Node t = tail时,同步队列还没有初始化后者正在进行初始化但是tail=head这一步还没有完成,t也为null。
  • 执行Node h = head时同步队列还没有进行初始化或者还没有完成初始化,导致h为null,在执行Node t = tail时同步队列已经完成了初始化,t不为null。这情况是h为null,t不为null,会导致执行s = h.next代码时发生空指针异常,其他的情况都不会发生
  • 执行Node h = head时同步队列正在进行初始化,完成了compareAndSetHead(new Node())这一步,h不为null,执行Node t = tail时,同步队列初始化的操作tail=head还没有完成,此时t为null。
    那么在hasQueuedPredecessors方法中,如果先执行Node t = tail,再执行Node h = head的话可能会存在的情况有:
  • 执行Node t = tail时同步队列已经完成了初始化,h,tail都不为null
  • 执行Node t = tail时同步队列没有进行初始化,t为null,执行Node h = head时同步队列还没有进行初始化。h为null
  • 执行Node t = tail时正在执行初始化完成了compareAndSetHead(new Node())这一步,但是还没有完成tail=head这一步,t为null。执行Node h = head时tail=head这一步完成了,h不为null
    从以上分析可以看出,先执行Node t = tail,再执行Node h = head的话是不存在,h为null,而t不为null这一种情况的,而先执行Node h = head,再执行Node t = tail的话,是会存在h为null,而t不为null这一种情况的

2.hasQueuedPredecessors方法false的情况

情况一: h != t条件不成立
也就是说此时同步队列还没有进行初始化,h,t都为null值或者h,t都指向head头节点,同步队列了除了一个头节点就没有其他的节点了。那么就不需要排队了。
情况二: h != t成立,(s = h.next) == null不成立并且s.thread != Thread.currentThread()也不成立。
h != t成立说明同步队列中至少有两个节点,(s = h.next) = = null不成立,说明h头节点存在后继节点,s.thread != Thread.currentThread()也不成立说明这个后继节点所代表的线程与当前线程是同一个,那就说明已经到了当前线程去尝试获取锁的时候了,自然就不需要排队了。

3.hasQueuedPredecessors方法true的情况

情况一: h != t成立,(s = h.next) == null成立
h != t成立说明同步队列中至少有两个节点或者同步队列还没有完成初始化。
(s = h.next) = = null成立说明当前头节点是没有后继节点的,这个情况发生的场景有:
场景一:
已经有一个线程在执行初始化操作了,但是只执行完了compareAndSetHead(new Node())这一步,还没有执行完tail = head;这一步,导致了h不为null,tail为null。这是同步队列还没有完成初始化导致的条件h != t成立,此时h.next也是为null的,但是这个时候表明已经是有线程在执行入队操作了,只是还没有入队完而已。那么当前线程需要排队。
场景二:
同步队列已经完成了初始化,但是由于释放锁等操作,导致此时同步队列只要一个头节点,head,tail都指向这个头节点。此时有一个线程执行入队操作了,这个线程刚执行完compareAndSetTail(pred, node)把tail指针指向了自己新建的节点,导致了h != t成立,但是还没有执行完pred.next = node,导致head节点的next为null,满足了(s = h.next) = = null成立,此时也是说明有线程在执行入队操作了,只是还没有入队完而已。那么当前线程还是需要排队
情况二: h != t成立,(s = h.next) == null不成立,s.thread != Thread.currentThread()成立
说明当前同步队列至少有两个节点并且头节点的后继节点不为null,并且说明这个后继节点所代表的线程跟当前的线程不是同一个。这个就是标准的情况了,同步队列中已经有线程在等着获取锁了,那么当前的线程只能去排队了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值