CountDownLatch、CyclicBarrier、Semaphore详解

CountDownLatch

CountDownLatch概念

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

CountDownLatch用法

CountDownLatch主要有2个方法,当一个或者多个线程调用await方法,调用线程会被阻塞。其他线程调用countDown方法回事计数器减一(调用countDown不会使线程阻塞),当计数器为0的时候,因调用await方法被阻塞的线程会被唤醒,继续执行

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

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

代码示例
以下一个场景,主线程需要最后执行:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程" + Thread.currentThread().getName() + "开始运行");
                    countDownLatch.countDown();
                }
            }, "t" + i).start();
        }

        countDownLatch.await();
        System.out.println("主线程最后执行-------------");
    }
}

运行结果:

线程t1开始运行
线程t4开始运行
线程t2开始运行
线程t3开始运行
线程t6开始运行
线程t5开始运行
主线程最后执行-------------

从结果可以得出,CountDownLatch可以控制线程执行先后,也就是调用了countDownLatch.await()的线程直到countDown计数器为0后才可以执行。

在看以下一个案例,大家知道,秦灭六过后才一统华夏,该案例也涉及到枚举的应用

枚举类

public enum  CountryEnum {
    ONE(1, "齐国"), TWO(2, "楚国"), THREE(3, "燕国"), FOUR(4, "韩国"), FIVE(5, "赵国"), SIX(6, "魏国");
    private int num;
    private String name;

    CountryEnum(int num, String name) {
        this.num = num;
        this.name = name;
    }

    public int getNum() {
        return num;
    }

    public String getName() {
        return name;
    }

    public static CountryEnum getCountryEnum(int num) {
        CountryEnum[] values = CountryEnum.values();
        for (CountryEnum value : values) {
            if (value.getNum() == num) {
                return value;
            }
        }
        return null;
    }
}

测试类

public class CountDownLatchTest02 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            int tempInt = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(CountryEnum.getCountryEnum(tempInt).getName() + "被灭");
                    countDownLatch.countDown();
                }
            }).start();
        }

        countDownLatch.await();
        System.out.println("秦灭六国,一统华夏");
    }
}

运行结果:

赵国被灭
燕国被灭
齐国被灭
楚国被灭
魏国被灭
韩国被灭
秦灭六国,一统华夏

注意枚举的使用,使用CountDownLatch可以控制主线程最后执行。

CyclicBarrier

CyclicBarrier概念

CyclicBarrier的字面意思是可循环的(Cyclic)的使用屏障(Barrier)。它主要做的事情是让一组线程到达一个屏障点(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障是通过CyclicBarrier的await方法实现的。

CyclicBarrier可以使一定数量的线程反复地在屏障位置处汇集。当线程到达屏障位置时将调用await方法,这个方法将阻塞直到所有线程都到达屏障位置。如果所有线程都到达屏障位置,那么屏障将打开,此时所有的线程都将被释放。

代码示例
public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始开会");
            }
        }));

        for (int i = 1; i <= 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入会议室");
                        // 被阻塞,等待所有的线程都进入会议室
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }, "t"+ i).start();
        }
    }
}

运行结果:

t1进入会议室
t4进入会议室
t5进入会议室
t3进入会议室
t2进入会议室
开始开会

从结果可以看出,当所有的线程都调用了 await方法,cyclicBarrier构造方法的线程才会执行,同时所有调用await方法的线程同时被唤醒,继续执行下面的操作

注意 :

  1. CyclicBarrier的构造方法,可以传入一个实现了Runnable接口的类,表示,当所有的线程都调用了await,该线程才会被被执行。
  2. CyclicBarrier的构造方法也可以只传入一个int类型的数字,表示当所有的线程(传入的参数数字的线程)都调用了await,所有的调用了await方法的线程都会被唤醒,继续执行下面的代码

Semaphore

Semaphore主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程的控制数量。

Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

工作原理

以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。这个停车系统中,每辆车就好比一个线程,看门人就好比一个信号量,看门人限制了可以活动的线程。假如里面依然是三个车位,但是看门人改变了规则,要求每次只能停两辆车,那么一开始进入两辆车,后面得等到有车离开才能有车进入,但是得保证最多停两辆车。对于Semaphore类而言,就如同一个看门人,限制了可活动的线程数。

Semaphore主要方法
  • Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。

  • Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。

  • void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。

  • void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。

  • void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。

  • void release(int n):释放n个许可。

  • int availablePermits():当前可用的许可数。

代码示例
public class SemaphoreTest {
    public static void main(String[] args) {
        // 表示3个车位
        Semaphore semaphore = new Semaphore(3);

        // 创建5个线程,一个线程代表一辆车
        for (int i = 1; i <= 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 获得许可,在获取许可前该线程将一直阻塞在此,表示抢占车位
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + " 占用了车位");
                        Thread.sleep(3000);
                        System.out.println(Thread.currentThread().getName() + " 停车3秒离开车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 表示释放一个许可,表示离开车位
                        semaphore.release();
                    }
                }
            }, "t"+i).start();
        }
    }
}

运行结果:

t1 占用了车位
t2 占用了车位
t3 占用了车位
t1 停车3秒离开车位
t2 停车3秒离开车位
t3 停车3秒离开车位
t4 占用了车位
t5 占用了车位
t4 停车3秒离开车位
t5 停车3秒离开车位

从运行结果可知,同时只能有3个线程(即车)进入停车,离开一辆车才能进来一辆车。

semaphore.acquire()semaphore.release()包起来的代码块,同时只能有3个线程进来,其他线程进来必须等待里面的线程执行完释放了资源,才能进入。

semaphore和synchronize的区别
semaphore:可以控制多个线程进入,也可以是一个线程进入代码块
synchronize:只能控制一个线程计入代码块

总结:
Semaphore主要用于控制当前活动线程数目,就如同停车场系统一般,而Semaphore则相当于看守的人,用于控制总共允许停车的停车位的个数,而对于每辆车来说就如同一个线程,线程需要通过acquire()方法获取许可,而release()释放许可。如果许可数达到最大活动数,那么调用acquire()之后,便进入等待队列,等待已获得许可的线程释放许可,从而使得多线程能够合理的运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值