欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java并发工具类:CountdownLatch,CyclicBarrier,Semaphore

程序员文章站 2024-03-12 13:31:14
...

在jdk开发包java.util.concurrent中提供了几个非常有用的并发工具类:

  • CountdownLatch
  • CyclivcBarrier
  • Semaphore

接下来一一解释其特性和用法:

1.CountdownLatch

1.1介绍
java并发工具类:CountdownLatch,CyclicBarrier,Semaphore
这是jdk文档的相关介绍,简而言之就是:

  • CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用),CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现,计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。 计数器初始值为线程的数量。这时任何调用这个对象上的await()方法都会阻塞进入阻塞队列,如果一个线程调用了countDown()方法,则会使计数器-1;当计数器的值为0时,这时候阻塞队列中调用await()方法的线程便会逐个被唤醒,从而进入后续的操作

1.2CountDownLatch的用法

  • 1.某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,计数器减一countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行

  • 2.实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

1.3CountDownLatch应用场景

1.3.1主线程等待子线程执行完成在执行:

public class countDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        for(int i = 0;i < 3;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "已完成任务,数量减一");
                countDownLatch.countDown();
            }).start();
        }
        System.out.println("主线程开始等待...");
        countDownLatch.await(); // 等待计数器归零,然后再向下执行
        System.out.println("主线程等待结束,即将关闭...");
    }
}

查看输出代码:

主线程开始等待...
Thread-0已完成任务,数量减一
Thread-1已完成任务,数量减一
Thread-2已完成任务,数量减一
主线程等待结束,即将关闭...

很简单的一段代码,await()使主线程阻塞,当countDownLatch里的计数为0时,主线程被唤醒,继续执行主线程里的代码,这里的计数次数是根据线程数量设定的,可以自定义,假如设定为4,程序就会一直阻塞,可以设置await()参数值规定时间 ,时间到后就不再阻塞,比如await(1000,TimeUnit.MILLISECONDS);在1s后不再阻塞
1.3.2赛跑

public class countDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        for(int i = 1;i < 6;i++){
            new Thread(new runner(latch),""+i).start();
        }
        new Thread(new Boss(latch)).start();
        Thread.sleep(2000);
        System.out.println("比赛结束");
    }
}
class runner implements Runnable{
    CountDownLatch countDownLatch;

    public runner(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"准备就绪");
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "出发!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "到达终点");
        countDownLatch.countDown();
    }
}

class Boss implements Runnable{

    CountDownLatch countDownLatch;

    public Boss(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        System.out.println("裁判准备吹哨3.2.1...");
        countDownLatch.countDown();
    }
}

输出结果:

1准备就绪
2准备就绪
3准备就绪
4准备就绪
5准备就绪
裁判准备吹哨3.2.1...
1出发!
3出发!
5出发!
4出发!
2出发!
2到达终点
1到达终点
3到达终点
5到达终点
4到达终点
比赛结束

Process finished with exit code 0

2.CyclicBarrier

2.1介绍
官方的介绍很晦涩,我们可以看一下:

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.

翻译过来就是:
CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier很有帮助。这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的。
我们可以简单的将其理解为一个加法计数器:

  • 当累加到额定值(自定义)时,可以完成指定任务,然后归零等待下一次累加到额定值

2.2使用场景及用法

看例子:

public class cyclicDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(30,()->{
            System.out.println("人数够了,司机准备发车");
        });
        for(int i = 1;i <= 30;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "上车");
                try {
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName() + ":发车了,开心");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

查看结果:

Thread-28上车
Thread-29上车
人数够了,司机准备发车
Thread-29:发车了,好开心
Thread-1:发车了,好开心

通过这个例子,我们发现它的用法和CountdownLatch大同小异,下面介绍常用的几个方法:

  • CyclicBarrier(parties):初始化相互等待的线程数量的构造方法。
  • CyclicBarrier(parties,Runnable barrierAction):初始化相互等待的线程数量以及屏障线程的构造方法,后者是一个runnable接口,重写run方法就是计数器满了后CyclicBarrier要执行的逻辑
  • await():调用这个方法的线程会阻塞

3.Semaphore

为了让大家好理解,可以把Semaphore理解为一个停车场:只能停规定数量的车,其他的车只有当停车场里的车离开才能进去,举个例子

public class Demo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//规定容量为3
        for(int i = 1;i <= 6;i++){
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },""+i).start();
        }
    }
}

查看结果:

1抢到车位
3抢到车位
2抢到车位
3离开车位
1离开车位
2离开车位
5抢到车位
4抢到车位
6抢到车位
4离开车位
6离开车位
5离开车位

这和我们介绍的一样,我们看一下其中的两个方法:

  • semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止!
  • semaphore.release():释放,释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!