Java JUC包源码分析 - 倒计时器CountDownLatch

倒计时器CountDownLatch是让一个或多个线程等待其他的线程执行完后再开始继续执行,是基于共享锁实现的。

话不多说,先看下怎么使用,下面是自己写的一个Demo

package com.pzx.test004;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss-");
    static CountDownLatch countDownLatch = new CountDownLatch(5);
    public static void main(String[] args) {

        System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " started...");
        MyThread myThread = new MyThread(countDownLatch);
        for (int i=0; i<5; i++) {
            Thread thread = new Thread(myThread);
            thread.setName("t"+i);
            thread.start();
        }
        System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " await...");
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " continue...");
    }

    static class MyThread implements Runnable {
        CountDownLatch cdl;
        public MyThread(CountDownLatch cdl) {
            this.cdl = cdl;
        }

        @Override
        public void run() {
            System.out.println(sdf.format(new Date())+Thread.currentThread().getName()+" start");
            try {
                System.out.println(sdf.format(new Date())+Thread.currentThread().getName()+" working");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cdl.countDown();
        }
    }
}

某一次输出结果:

2018-09-11 10:21:11-main started...
2018-09-11 10:21:11-t0 start
2018-09-11 10:21:11-t0 working
2018-09-11 10:21:11-t1 start
2018-09-11 10:21:11-t1 working
2018-09-11 10:21:11-t3 start
2018-09-11 10:21:11-t3 working
2018-09-11 10:21:11-main await...
2018-09-11 10:21:11-t2 start
2018-09-11 10:21:11-t2 working
2018-09-11 10:21:11-t4 start
2018-09-11 10:21:11-t4 working
2018-09-11 10:21:16-main continue...

从结果来看,main线程调用countdownlatch的await()后就在等待了,直到其他线程把倒计时数count(其实是AQS里的state)置0后才继续运行,也就是一定要调用至少5次countdown()方法。

接下来看下源码:

主要围绕await和countdown方法深入分析(其实也就是分析AQS共享锁部分和CountDownLatch重写的方法)

public class CountDownLatch {
    // 内部类Sync,作为同步控制器,使用AQS的state来表示count
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        // 构造函数就是直接设置AQS的state的值
        Sync(int count) {
            setState(count);
        }
        // 获取state值
        int getCount() {
            return getState();
        }
        // 重写了AQS的方法,尝试获取共享锁,如果state等于0就相当于尝试获取成功
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        // 同样的重写AQS的方法,尝试释放共享锁,把state-1并设置,如果减到0了就true
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                // 一进去发现state=0,没什么好释放的,返回false
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;
    // 构造方法就是新建一个Sync对象,设置好AQS的state
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    // await方法是取尝试获取共享锁,如果失败则使得当前线程阻塞,直到获取成功才返回
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // countDown方法是释放共享锁
    public void countDown() {
        sync.releaseShared(1);
    }
}

    // 看AQS里面的获取共享锁的方法
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 如果线程中断了,则清楚中断标志并抛出中断异常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取共享锁,也就是上面重写的方法,state==0才返回1,这时就代表可获取共享锁
        // await相当于执行结束,这是当调用countdown()达到规定次数了才会state=0的,否则返回-1
        if (tryAcquireShared(arg) < 0)
            // 去获取共享锁
            doAcquireSharedInterruptibly(arg);
    }

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 以共享模式加入到CLH队列的尾巴上,返回加入的那个节点
        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就代表可以获取了,设置当前节点为头结点
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 如果前置节点不是头结点或获取锁失败,则判断是否要阻塞,要的话就去阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


   // countdown方法主要是调用AQS的释放共享锁方法releaseShared
   public final boolean releaseShared(int arg) {
        // 尝试释放锁,见上面的重写的方法,tryReleaseShared返回true时,state已经为0了,
        // 这时就要唤醒等待的线程了(即Demo中调用await方法的主线程)
        if (tryReleaseShared(arg)) {
            // 去释放共享锁
            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;
                // 如果头结点处于需要唤醒后继节点的状态就设置它的waitStatus为0,然后唤醒h的后继
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 唤醒h的后继线程
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

 

你可能感兴趣的:(Java源码分析,Java多线程)