面试之闭锁 通关(CountDownLatch)

在实际的项目场景中,我们有时候会有以下类似的场景需求:

  • 日终统计时,有多个线程并发统计各分类情况,有一个线程需要等待每一类都统计完成后进行合计;
  • 或者多个线程检查周边系统或应用启动情况,待所有周边系统启动成功后再启动当前应用;

针对以上类似场景JDK提供了非常好用的两个工具类:CountDownLatch、CyclicBarrier,那两个之前有什么区别呢,我们看一下官方解释:


public class CountDownLatch
extends Object
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
//一个同步的帮助,允许一个或多个线程等待,直到在其他线程中执行一组操作完成。
A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon -- the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.
//一个countdown门闩是用一个给定的计数初始化的。等待方法阻塞,直到当前计数达到零,因为调用倒计时()方法,在此之后,所有等待线程都被释放,随后的任何后续调用都会立即返回。这是一个单次使用场景,计数不能被重置。如果您需要一个重新设置计数的版本,可以考虑使用一个周期屏障(CyclicBarrier)。

public class CyclicBarrier
extends Object
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.
//一种同步的帮助,允许一组线程相互等待,从而达到一个共同的障碍点。在涉及固定大小的线程的程序中,周期性障碍是有用的,这些线程必须等待。这个屏障可以循环,因为它可以在等待线程被释放后重新使用。

可以看出CountDownLatch只能使用一次,但CyclicBarrier强调的循环(可多次使用),我们来做个小demo来看一下。

CountDownLatch:

从字面意思就可以看出来,CountDownLatch是一个计数器,CountDownLatch的源码也比较少,只提供一个构造函数和两个常用方法:

看下实际使用,假如7个球员约定去踢足球,只有等7个人到齐了才能开场,我们先来看下如果没有CountDownLatch如何实现以上场景:

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试闭锁
 *
 * @author
 * @create 2018-09-18 14:14
 **/
public class TestCountDownLatch {

    public static void main(String[] args) {

        //七个人开始出发了
        FootballBoy footballBoy = new FootballBoy();
        for (int i=1;i<=7;i++) {
            new Thread(footballBoy,"第" + i + "人").start();
        }
        //场地管理员等着所有人到达
        while (true) {
            if(footballBoy.count.intValue()== 7) {
                System.out.println("人到齐了,打开场地开始踢球");
                break;
            }
            System.out.println("等待中....");
        }
    }
}

class FootballBoy implements Runnable {

    /**
     * 搞一个共享变量来计数
     */
    public AtomicInteger count = new AtomicInteger(0);

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "到达场地");
        //到达现场报数+1
        count.getAndIncrement();
    }
}

------------------执行结果--------------------

第1人到达场地
第3人到达场地
第2人到达场地
等待中....
等待中....
等待中....
等待中....
等待中....
第5人到达场地
等待中....
等待中....
等待中....
第7人到达场地
等待中....
等待中....
等待中....
第6人到达场地
等待中....
等待中....
等待中....
等待中....
第4人到达场地
等待中....
人到齐了,打开场地开始踢球


这样写起来比较麻烦,看一下使用CountDownLatch之后的demo:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试闭锁
 * @author jifengzhu
 * @create 2018-09-18 15:24
 **/
public class TestCountDownLatch {

    public static void main(String[] args) {
        //需要提前知道执行线程数
        CountDownLatch countDownLatch = new CountDownLatch(7);
        FootballBoy footballBoy = new FootballBoy(countDownLatch);
        for (int i=1;i<=7;i++) {
            new Thread(footballBoy,"第" + i + "人").start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            //异常处理
        }
        System.out.println("人到齐了,打开场地开始踢球...");
    }

}

class FootballBoy implements Runnable {

    private CountDownLatch countDownLatch;

    public FootballBoy(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "到达场地");
        }catch (Exception e) {
            //异常处理
        }finally {
            countDownLatch.countDown();
        }
    }
}

-----------------执行结果---------------
第2人到达场地
第6人到达场地
第3人到达场地
第4人到达场地
第7人到达场地
第1人到达场地
第5人到达场地
人到齐了,打开场地开始踢球...

通过上面的demo我们可以看出countDownLatch.await();代码之后的程序是没有执行的,直到CountDownLatch的计数器为0才执行,看API和demo发现CountDownLatch不能重复使用,但是如果要实现全场比赛,包括中场休息,休息完回来再开下半场,此时会发现CountDownLatch已经不能满足了,JDK此时提供了另外一个工具类CyclicBarrier:

import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试循环屏障
 *
 * @author
 * @create 2018-09-18 18:12
 **/
public class TestCyclicBarrier {

    public static void main(String[] args) {

        Thread event = new Thread(new Event());
        CyclicBarrier barrier = new CyclicBarrier(7,event);

        for (int i=1;i<=7;i++) {
            new Thread(new FootballBoyer(barrier,i)).start();
        }
    }
}

/**
 * 比赛事件
 */
class Event implements Runnable {

    public static int flag = 1;

    @Override
    public void run() {
        switch (flag) {
            case 1:
                System.out.println("球员到齐,开始比赛......");
                flag++;
                break;
            case 2:
                System.out.println("中场时间到,中场休息......");
                flag++;
                break;
            case 3:
                System.out.println("大家都累了,比赛结束......");
                break;
        }
    }
}

/**
 * 球员
 */
class FootballBoyer implements Runnable {

    private CyclicBarrier barrier;
    private int number;

    public FootballBoyer(CyclicBarrier barrier,int number) {
        this.barrier = barrier;
        this.number = number;
    }
    @Override
    public void run() {
        try {
            System.out.println(number + "号球员到达场地.");
            barrier.await();
            firstHalf(barrier);
            secondHalf(barrier);
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private void firstHalf(CyclicBarrier barrier) {
        System.out.println("上半场" + number + "号球员" + getAction(number));
        try {
            barrier.await();
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private void secondHalf(CyclicBarrier barrier) {
        System.out.println("下半场" + number + "号球员" + getAction(number));
        try {
            barrier.await();
        } catch (InterruptedException e) {
        } catch (BrokenBarrierException e) {
        }
    }

    private String getAction(int number) {
        if(number%2==0) {
            return "踢球";
        }else {
            return "运球";
        }
    }
}


--------------------执行结果-------------------------------
2号球员到达场地.
1号球员到达场地.
4号球员到达场地.
5号球员到达场地.
6号球员到达场地.
3号球员到达场地.
7号球员到达场地.
球员到齐,开始比赛......
上半场7号球员运球
上半场2号球员踢球
上半场1号球员运球
上半场6号球员踢球
上半场5号球员运球
上半场4号球员踢球
上半场3号球员运球
中场时间到,中场休息......
下半场3号球员运球
下半场7号球员运球
下半场5号球员运球
下半场6号球员踢球
下半场2号球员踢球
下半场1号球员运球
下半场4号球员踢球
大家都累了,比赛结束......

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值