JUC学习(二):线程同步之CountDownLatch

目录

一.简述

二.构造方法

三.await方法

四.countDown方法


一.简述

在 JUC学习(一):Semaphore信号量 中可以看到,信号量可以做到控制不同批次的线程同步运行,但是对于同一批线程,它们之间还是乱序执行的。在某些情况下,需要控制线程同时开始运行,这时候就需要使用CountDownLatch。

从名字来看,这是一个计数值只降不升的“闩”,可以猜到,它的使用方式就是设置计数器值,每有一个线程准备就绪,就将计数器值下降,当计数器清零时,门闩打开,线程们就可以同时开始执行。下面是示例代码:

public class CountDownLatchTest {
    private static CountDownLatch down = new CountDownLatch(1);

    public static void testMethod(){
        try{
            System.out.println("等待"+Thread.currentThread().getName()+"准备就绪");
            down.await();
            System.out.println(Thread.currentThread().getName()+"开始运行!");
        }catch (Exception e){

        }
    }

    public static void downMethod(){
        System.out.println("线程"+Thread.currentThread().getName()+"要叫醒子线程了!");
        down.countDown();
    }

    static class MyThread extends Thread{
        @Override
        public void run() {
            testMethod();
        }
    }

    public static void main(String args[]) throws InterruptedException {
        new MyThread().start();
        try {
            Thread.sleep(2000);
            downMethod();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

运行结果如下:

等待Thread-0准备就绪
线程main要叫醒子线程了!
Thread-0开始运行!

可以看到,Thread-0在调用down.await()后,就进入等待状态,不再继续运行。主线程sleep两秒钟后,通过调用down.countDown()将计数器清零,这时才唤醒Thread-0,使其继续工作。

假如计数器清零之后再运行countDown,则不会有任何效果。

由此可见,await和countDown是CountDownLatch的核心方法。

二.构造方法

在构造CountDownLatch实例时,传入的也是一个int值,联系Semaphore,不难猜测它们原理大致相同。看源码果然如此:

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

Sync类和Semaphore一样,也是一个AQS抽象子类,Sync的构造方法也是设置AQS的state变量值。

不同的是,信号量在构造时并没有限制permits必须大于等于0,而CountDownLatch由于count值只降不升,因此必须保证初始值不小于0。

三.await方法

await方法的效果是阻塞当前线程,直到计数器清零。很容易联想到信号量的acquire方法,相反的是,acquire是阻塞线程,直到计数器值大于等于申请许可数。因此,它们都调用了AQS的同名方法;await的重载版本接受时间作为参数,表示等待若干时间后结束等待,和tryAcquire类似,相应的,它们也调用了同名方法:

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

tryAcquireSharedNanos源码以及acquireSharedInterruptibly调用的doAcquireSharedInterruptibly方法源码与 JUC学习(一):Semaphore信号量 相同,不再赘述。

下面看下acquireSharedInterruptibly调用的tryAcquireShared:

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

代码只有一行,就是判断当前计数器值是否为0,是则继续执行线程任务,否则阻塞线程直到计数器清零。

四.countDown方法

既然await对应了acquire,那么countDown自然对应了release方法。与await类似,CountDownLatch也仅重写了try方法:

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

首先判断当前计数值是否已经为0,是就没必要继续运行,直接退出。然后CAS地将计数值减1,并返回新计数值是否为0。假如计数值为0,那么就通过doReleaseShared方法,将阻塞队列中的线程唤醒。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值