CountDownLatch Analysis

CountDownLatch is a tool class in the concurrent package, which is used to control the logic that multiple threads continue to execute after other tasks are completed.

public class CountDownLatchTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        CountDownLatch latch=new CountDownLatch(2);
        System.out.println(Thread.currentThread().getName()+" before latch");
        Thread t2=new CountDownLatchTest().new Thread2(latch);
        t2.start();
         Thread t1= new CountDownLatchTest().new Thread1(latch);
        t1.start();




        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


        System.out.println(Thread.currentThread().getName()+" after latch");

    }

    public class Thread2 extends Thread{

        /**
         * @param cy
         */
        public Thread2(CountDownLatch latch) {
            super();

            this.latch=latch;
        }


        CyclicBarrier cy;
        CountDownLatch latch;


        @Override
        public void run(){
            System.out.println("thread2 before countDown");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            latch.countDown();


            System.out.println("thread2 after countDown11111");
            latch.countDown();
            System.out.println("thread2 after countDown222222");

        }
    }



    public class Thread1 extends Thread{

        /**
         * @param cy
         */
        public Thread1(CountDownLatch latch) {
            super();

            this.latch=latch;
        }


        CyclicBarrier cy;
        CountDownLatch latch;


        @Override
        public void run(){
            System.out.println("thread1 before await");

            try {
                latch.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }


            System.out.println("thread1 after await11111");


        }
    }

}

As shown in the above code, for a CountdownLatch with an initialization parameter of 2, the main thread and Thread1 will block after calling the await() method of CountdownLatch, until the CountdownLatch of Thread2 has executed countDown() twice, these two threads will not Go ahead.
Looking at the source code of CountdownLatch, it can be seen that its implementation is based on AQS. Its two basic operations await and countdown are operations on the AQS-based inner class Sync. Determine whether to block and release the thread by judging the state of Sync (its initialization value is i in CountDownLatch latch=new CountDownLatch(i);).
First look at the implementation of its inner class Sync:

  private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

//共享式获取同步状态,当getState=0时,说明state值已经被countdown消费完,可以获取同步状态,返回1,否则返回-1
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;  
        }

//共享式释放同步状态,当通过cas原子操作设置--state=0时候,返回true
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

Mainly focus on the tryAcquireShared and tryReleaseShared methods
The following focuses on the await method. It will eventually call the acquireSharedInterruptibly() method of AQS:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)   //如果小于0,表明state的值没消费完
            doAcquireSharedInterruptibly(arg);  // 那就将当前线程放入abs队列,并自旋式的获取同步状态
    }


    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); //那就尝试获取同步状态,当state==0时返回1,否则返回-1.
                    if (r >= 0) {   //如果大于0,表明countdown消费完state的值
                        setHeadAndPropagate(node, r);   //释放当前节点并传播
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) && //如果state!=0,则让线程进入等待队列
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

The source code for calling the countdown operation is as follows: the following aspects of abs will be called

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { //tryReleaseShared每次操作都是将state--,如果state=0,则返回true,说明state已经被countdown消费完,所有等待线程都释放。
            doReleaseShared();
            return true;
        }
        return false;
    }


    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325900960&siteId=291194637