JUC经常使用同步工具类——CountDownLatch,CyclicBarrier,Semaphore

在 JUC 下包含了一些经常使用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别。程序员

1、CountDownLatch

先看一下,CountDownLatch 源码的官方介绍。dom

意思是,它是一个同步辅助器,容许一个或多个线程一直等待,直到一组在其余线程执行的操做所有完成。ide

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

它的构造方法,会传入一个 count 值,用于计数。函数

经常使用的方法有两个:工具

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

public void countDown() {
    sync.releaseShared(1);
}复制代码

当一个线程调用await方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。学习

如今设想一个场景,公司项目,线上出现了一个紧急 bug,被客户投诉,领导焦急的过来,想找人迅速的解决这个 bug 。ui

那么,一我的解决确定速度慢啊,因而叫来张三和李四,一块儿分工解决。终于,当他们两个都作完了本身所须要作的任务以后,领导才能够答复客户,客户也就消气了(没办法啊,客户是上帝嘛)。this

因而,咱们能够设计一个 Worker 类来模拟单我的修复 bug 的过程,主线程就是领导,一直等待全部 Worker 任务执行结束,主线程才能够继续往下走。spa

public class CountDownTest {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        Worker w1 = new Worker("张三", 2000, latch);
        Worker w2 = new Worker("李四", 3000, latch);
        w1.start();
        w2.start();

        long startTime = System.currentTimeMillis();
        latch.await();
        System.out.println("bug所有解决,领导能够给客户交差了,任务总耗时: "+ (System.currentTimeMillis() - startTime));

    }

    static class Worker extends Thread{
        String name;
        int workTime;
        CountDownLatch latch;

        public Worker(String name, int workTime, CountDownLatch latch) {
            this.name = name;
            this.workTime = workTime;
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));
            doWork();
            System.out.println(name+"结束修复bug,当前时间: "+sdf.format(new Date()));
            latch.countDown();
        }

        private void doWork() {
            try {
                //模拟工做耗时
                Thread.sleep(workTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}复制代码

原本须要 5 秒完成的任务,两我的 3 秒就完成了。我只能说,这程序员的工做效率真是太太过高了。线程

2、CyclicBarrier

barrier 英文是屏障,障碍,栅栏的意思。cyclic是循环的意思,就是说,这个屏障能够循环使用(什么意思,等下我举例子就知道了)。源码官方解释是:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point . The barrier is called cyclic because it can be re-used after the waiting threads are released.

一组线程会互相等待,直到全部线程都到达一个同步点。这个就很是有意思了,就像一群人被困到了一个栅栏前面,只有等最后一我的到达以后,他们才能够协力把栅栏(屏障)突破。

CyclicBarrier 提供了两种构造方法:

public CyclicBarrier(int parties) {
    this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}复制代码

第一个构造的参数,指的是须要几个线程一块儿到达,才可使全部线程取消等待。第二个构造,额外指定了一个参数,用于在全部线程达到屏障时,优先执行 barrierAction。

如今模拟一个经常使用的场景,一组运动员比赛 1000 米,只有在全部人都准备完成以后,才能够一块儿开跑(额,先忽略裁判吹口哨的细节)。

定义一个 Runner 类表明运动员,其内部维护一个共有的 CyclicBarrier,每一个人都有一个准备时间,准备完成以后,会调用 await 方法,等待其余运动员。 当全部人准备都 OK 时,就能够开跑了。

public class BarrierTest {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3);  //①
        Runner runner1 = new Runner(barrier, "张三");
        Runner runner2 = new Runner(barrier, "李四");
        Runner runner3 = new Runner(barrier, "王五");

        ExecutorService service = Executors.newFixedThreadPool(3);
        service.execute(runner1);
        service.execute(runner2);
        service.execute(runner3);

        service.shutdown();

    }

}


class Runner implements Runnable{

    private CyclicBarrier barrier;
    private String name;

    public Runner(CyclicBarrier barrier, String name) {
        this.barrier = barrier;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            //模拟准备耗时
            Thread.sleep(new Random().nextInt(5000));
            System.out.println(name + ":准备OK");
            barrier.await();
            System.out.println(name +": 开跑");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e){
            e.printStackTrace();
        }
    }
}复制代码

能够看到,咱们已经实现了须要的功能。可是,有的同窗就较真了,说你这不行啊,哪有运动员都准备好以后就开跑的,你还把裁判放在眼里吗,裁判不吹口哨,你敢跑一个试试。

好吧,也确实是这样一个理儿,那么,咱们就实现一下,让裁判吹完口哨以后,他们再一块儿开跑吧。

这里就要用到第二个构造函数了,因而我把代码 ① 处稍微修改一下。

CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println("等裁判吹口哨...");
            //这里停顿两秒更便于观察线程执行的前后顺序
            Thread.sleep(2000);
            System.out.println("裁判吹口哨->>>>>");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});复制代码

执行结果:

张三:准备OK
李四:准备OK
王五:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
张三: 开跑
李四: 开跑
王五: 开跑复制代码

能够看到,虽然三我的都已经准备 OK了,可是,只有裁判吹完口哨以后,他们才能够开跑。

刚才,提到了循环利用是怎么体现的呢。 我如今把屏障值改成 2,而后增长一个“赵六” 一块儿参与赛跑。被修改的部分以下:

此时观察,打印结果:

张三:准备OK
李四:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
李四: 开跑
张三: 开跑
王五:准备OK
赵六:准备OK
等裁判吹口哨...
裁判吹口哨->>>>>
赵六: 开跑
王五: 开跑复制代码

发现没,能够分两批,第一批先跑两我的,而后第二批再跑两我的。也就是屏障的循环使用。

3、Semaphore

Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,通常可用于流量的控制。

打个比方,如今有一段公路交通比较拥堵,那怎么办呢。此时,就须要警察叔叔出面,限制车的流量。

好比,如今有 20 辆车要经过这个地段, 警察叔叔规定同一时间,最多只能经过 5 辆车,其余车辆只能等待。只有拿到许可的车辆可经过,等车辆经过以后,再归还许可,而后把它发给等待的车辆,得到许可的车辆再通行,依次类推。

public class SemaphoreTest {
    private static int count = 20;

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(count);

        //指定最多只能有五个线程同时执行
        Semaphore semaphore = new Semaphore(5);

        Random random = new Random();
        for (int i = 0; i < count; i++) {
            final int no = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //得到许可
                        semaphore.acquire();
                        System.out.println(no +":号车可通行");
                        //模拟车辆通行耗时
                        Thread.sleep(random.nextInt(2000));
                        //释放许可
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();

    }
}复制代码

打印结果我就不写了,须要读者自行观察,就会发现,第一批是五个车同时通行。而后,后边的车才能够依次通行,可是同时通行的车辆不能超过 5 辆。

细心的读者,就会发现,这许可一共就发 5 个,那等第一批车辆用完释放以后, 第二批的时候应该发给谁呢?

这确实是一个问题。全部等待的车辆都想先拿到许可,先通行,怎么办。这就须要,用到锁了。就全部人都去抢,谁先抢到,谁就先走呗。

咱们去看一下 Semaphore的构造函数,就会发现,能够传入一个 boolean 值的参数,控制抢锁是不是公平的。

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}复制代码

默认是非公平,能够传入 true 来使用公平锁。(锁的机制是经过AQS,如今不细讲,等我之后更新哦)

这里简单的说一下,什么是公平非公平吧。

公平的话,就是你车来了,就按照先来后到的顺序正常走就好了。不公平的,也许就是,某位司机大哥膀大腰圆,手戴名表,脖子带粗金项链。别人一看惹不起,我还躲不起吗,都给这位大哥让道。你就算车走在人家前面了,你也不敢跟人家抢啊。

最后,那就是他先拿到许可证通行了。剩下的人再去抢,说不定又来一个,是警察叔叔的私交好友。(行吧行吧,其余司机只能一脸的生无可恋,怎么过个马路都这么费劲啊。。。)

总结

  1. CountDownLatch 是一个线程等待其余线程, CyclicBarrier 是多个线程互相等待。
  2. CountDownLatch 的计数是减 1 直到 0,CyclicBarrier 是加 1,直到指定值。
  3. CountDownLatch 是一次性的, CyclicBarrier 能够循环利用。
  4. CyclicBarrier 能够在最后一个线程达到屏障以前,选择先执行一个操做。
  5. Semaphore ,须要拿到许可才能执行,并能够选择公平和非公平模式。

若是本文对你有用,欢迎点赞,评论,转发。

学习是枯燥的,也是有趣的。我是「烟雨星空」,欢迎关注,可第一时间接收文章推送。

相关文章
相关标签/搜索