Why the parameter of `h` will judge whether it is null twice in AbstractQueuedSynchronizer?

Viewed 103

Recently i am learning about concurrency.When i learn more about Semaphore,i have some questions.

This is the code of AbstractQueuedSynchronizer in JDK1.8 (line 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();
        }
    }

Why do it judge h whether it is null twice here? When h can will be null? I think neither of them can be null.

2 Answers

Beacause the head node is dynamic move , invoke doReleaseShared() come from two sides:

1.held the lock's Thread invoke release()then execute doReleaseShared().

2.someone Thread execute acquire() ,preNode is head and acquire lock success the lock and then execute doReleaseShared();

Considering the following possible order of execution:

here is some node : head--->init node--->node1--->node2

someone release lock and then wake up the node1,the node1 resumed from unpark,(node1's Thread named Thread1,node2's Thread named Thread2...) node1's preNode is head,and acquire success,permit is one at the point.

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);
        }
    }

while the Thread1 is temporarily suspended after execute 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();
        }
    }

another Thread release lock.current head is node1,CAS set node1's waitStatus==0,and wake up node2 .

node2 preHead is node1 and acquire lock success.then execute setHeadAndPropagate() method .

It happened that Node node = head,and the Thread2 continue...

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

so let's back

 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);
        }
    }

finally,we back to Thread2,Thread2 continue.

when node2 judge the if(...) in setHeadAndPropagate() method line 990.

the h==null maybe occur. so doReleaseShared()

Summary: as your requestion ,the first h means the old head ,it could be null(Special scenario, thread scheduling caused by time slicing),but the subsequently (h=head==null) must not be null,beacause it's the current Thread2,means the currently alive.you can think of it as a routine examination,don't mind.

First time head is checked (Node h = head), next time - probably node (setHead(node)).

Related