java - 为什么 `h`这个参数在AbstractQueuedSynchronizer中会判断两次是否为null?

标签 java concurrency

最近我在学习并发。当我对Semaphore了解更多时,我有一些问题。

这是JDK1.8中AbstractQueuedSynchronizer的代码(727行):

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
       
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

这里为什么要判断h两次是否为null? h 什么时候可以为空?我认为它们都不能为空。

最佳答案

因为头节点是动态移动的, 调用 doReleaseShared() 来自两个方面:

1.持有锁的线程调用release()然后执行doReleaseShared()

2.someone Thread执行acquire(),preNode为head获取锁成功获取锁后执行doReleaseShared();

考虑以下可能的执行顺序:

这是一些节点: head--->init node--->node1--->node2

有人释放锁然后唤醒node1,node1从unpark中恢复,(node1的Thread named Thread1,node2的Thread named Thread2 ...) node1的preNode为head,获取成功,此时permit为1。

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);//success , r=0;
                    if (r >= 0) { //true
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

而 Thread1 在执行 setHead(node) 后暂时挂起。

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Thread2 come here and the Thread1 execute continue
        setHead(node);

        //Note: this point,the setHead(node) is done,but time slice 
        //      exhaustion,Thread1 is temporarily suspended 
                     
       
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

另一个Thread释放锁,当前head为node1,CAS设置node1的waitStatus==0,唤醒node2。

node2 preHead为node1,获取锁成功,然后执行setHeadAndPropagate()方法。

碰巧节点node = head,Thread2继续...

if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
//return immediate ,because 
//to old head
//propagate=0 false
//h==null fase
//h.waitStatus=0 false
//to new head node1
//propagate=0 false
//h==null fase
//h.waitStatus=0 false

我们回去吧

 private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC //node1 could be GC!!!
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

最后,我们回到Thread2,Thread2继续。

当node2在setHeadAndPropagate()方法第990行判断if(...)

h==null 可能会发生。所以 doReleaseShared()

总结: 根据你的要求,第一个h表示旧的head,它可以是null(特殊场景,时间分片引起的线程调度),但是后面的(h=head==null) 不能为null,因为它是当前的Thread2,表示当前还活着。你可以把它当作例行检查,不要介意。

关于java - 为什么 `h`这个参数在AbstractQueuedSynchronizer中会判断两次是否为null?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62770269/

相关文章:

Ruby:并发/多线程任务的 CPU 负载下降?

java - 如何最有效地添加一些信息到Java中的多线程列表?

java - 如何检查相同的 ID,然后为该特定 id 递增?

java - C3P0 创建过多的线程和定时器

java - 检测 JTextPane 中的行数

java - 判断字符串是否为有效日期的最快方法

c# - DateTime 可以在 64 位环境中撕裂吗?

java - 在 Java 中,如何定义返回多个答案中最佳答案的 Future<V>?

java - 在多线程环境中初始化EntityManagerFactory

java - 在java Swing中检测重叠对象